X-Git-Url: http://git.cyclocoop.org/%22%20.%20generer_url_ecrire%28%22auteur_infos%22%2C%20%22id_auteur=%24id%22%29%20.%20%22?a=blobdiff_plain;f=includes%2FParser.php;h=a910afe259a1f134780687ea714c36b052986add;hb=798df26f95341980b8e19373193bf23b00797973;hp=0e0661e26c7452c893f068184795bfcda60ac8a2;hpb=f2baa43923c0c0176959147449022b050c581314;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/Parser.php b/includes/Parser.php index 0e0661e26c..a910afe259 100644 --- a/includes/Parser.php +++ b/includes/Parser.php @@ -1,17 +1,20 @@ @@ -98,6 +101,7 @@ class Parser # Cleared with clearState(): var $mOutput, $mAutonumber, $mDTopen, $mStripState = array(); var $mVariables, $mIncludeCount, $mArgStack, $mLastSection, $mInPre; + var $mInterwikiLinkHolders, $mLinkHolders; # Temporary: var $mOptions, $mTitle, $mOutputType, @@ -114,6 +118,7 @@ class Parser * @access public */ function Parser() { + global $wgContLang; $this->mTemplates = array(); $this->mTemplatePath = array(); $this->mTagHooks = array(); @@ -128,13 +133,24 @@ class Parser function clearState() { $this->mOutput = new ParserOutput; $this->mAutonumber = 0; - $this->mLastSection = ""; + $this->mLastSection = ''; $this->mDTopen = false; $this->mVariables = false; $this->mIncludeCount = array(); $this->mStripState = array(); $this->mArgStack = array(); $this->mInPre = false; + $this->mInterwikiLinkHolders = array( + 'texts' => array(), + 'titles' => array() + ); + $this->mLinkHolders = array( + 'namespaces' => array(), + 'dbkeys' => array(), + 'queries' => array(), + 'texts' => array(), + 'titles' => array() + ); } /** @@ -142,6 +158,11 @@ class Parser * to internalParse() which does all the real work. * * @access private + * @param string $text Text we want to parse + * @param Title &$title A title object + * @param array $options + * @param boolean $linestart + * @param boolean $clearState * @return ParserOutput a ParserOutput */ function parse( $text, &$title, $options, $linestart = true, $clearState = true ) { @@ -157,50 +178,51 @@ class Parser $this->mTitle =& $title; $this->mOutputType = OT_HTML; - $stripState = NULL; - global $fnord; $fnord = 1; + $this->mStripState = NULL; + //$text = $this->strip( $text, $this->mStripState ); // VOODOO MAGIC FIX! Sometimes the above segfaults in PHP5. $x =& $this->mStripState; $text = $this->strip( $text, $x ); - $text = $this->internalParse( $text, $linestart ); + $text = $this->internalParse( $text ); + + $text = $this->unstrip( $text, $this->mStripState ); + # Clean up special characters, only run once, next-to-last before doBlockLevels - if(!$wgUseTidy) { - $fixtags = array( - # french spaces, last one Guillemet-left - # only if there is something before the space - '/(.) (?=\\?|:|;|!|\\302\\273)/i' => '\\1 \\2', - # french spaces, Guillemet-right - "/(\\302\\253) /i"=>"\\1 ", - '/
/i' => '
', - '/
/i' => '
', - '/
/i' => '
', - '/<\\/center *>/i' => '
', - # Clean up spare ampersands; note that we probably ought to be - # more careful about named entities. - '/&(?!:amp;|#[Xx][0-9A-fa-f]+;|#[0-9]+;|[a-zA-Z0-9]+;)/' => '&' - ); - $text = preg_replace( array_keys($fixtags), array_values($fixtags), $text ); - } else { - $fixtags = array( - # french spaces, last one Guillemet-left - '/ (\\?|:|;|!|\\302\\273)/i' => ' \\1', - # french spaces, Guillemet-right - '/(\\302\\253) /i' => '\\1 ', - '/
/i' => '
', - '/<\\/center *>/i' => '
' - ); - $text = preg_replace( array_keys($fixtags), array_values($fixtags), $text ); - } + $fixtags = array( + # french spaces, last one Guillemet-left + # only if there is something before the space + '/(.) (?=\\?|:|;|!|\\302\\273)/' => '\\1 \\2', + # french spaces, Guillemet-right + '/(\\302\\253) /' => '\\1 ', + '/
/i' => '
', + '/<\\/center *>/i' => '
', + ); + $text = preg_replace( array_keys($fixtags), array_values($fixtags), $text ); + # only once and last $text = $this->doBlockLevels( $text, $linestart ); $this->replaceLinkHolders( $text ); + + $dashReplace = array( + '/ - /' => " – ", # N dash + '/(?<=[\d])-(?=[\d])/' => "–", # N dash between numbers + '/ -- /' => " — " # M dash + ); + $text = preg_replace( array_keys($dashReplace), array_values($dashReplace), $text ); + + # the position of the convert() call should not be changed. it + # assumes that the links are all replaces and the only thing left + # is the mark. $text = $wgContLang->convert($text); + $this->mOutput->setTitleText($wgContLang->getParsedTitle()); $text = $this->unstripNoWiki( $text, $this->mStripState ); + + $text = Sanitizer::normalizeCharReferences( $text ); global $wgUseTidy; if ($wgUseTidy) { $text = Parser::tidy($text); @@ -234,38 +256,76 @@ class Parser * @access private * @static */ - function extractTags($tag, $text, &$content, $uniq_prefix = ''){ + function extractTagsAndParams($tag, $text, &$content, &$tags, &$params, $uniq_prefix = ''){ $rnd = $uniq_prefix . '-' . $tag . Parser::getRandomString(); if ( !$content ) { $content = array( ); } $n = 1; $stripped = ''; + + if ( !$tags ) { + $tags = array( ); + } + + if ( !$params ) { + $params = array( ); + } + + // Hack to support short XML style tags + $text = preg_replace( "/<$tag(\\s+[^>]*|\\s*)\\/>/i", "<$tag\\1>", $text ); + + if( $tag == STRIP_COMMENTS ) { + $start = '//'; + } else { + $start = "/<$tag(\\s+[^>]*|\\s*)>/i"; + $end = "/<\\/$tag\\s*>/i"; + } while ( '' != $text ) { - if($tag==STRIP_COMMENTS) { - $p = preg_split( '//i', $p[1], 2 ); - } else { - $q = preg_split( "/<\\/\\s*$tag\\s*>/i", $p[1], 2 ); - } - $marker = $rnd . sprintf('%08X', $n++); - $content[$marker] = $q[0]; - $stripped .= $marker; $text = $q[1]; } } return $stripped; } + /** + * Wrapper function for extractTagsAndParams + * for cases where $tags and $params isn't needed + * i.e. where tags will never have params, like + * + * @access private + * @static + */ + function extractTags( $tag, $text, &$content, $uniq_prefix = '' ) { + $dummy_tags = array(); + $dummy_params = array(); + + return Parser::extractTagsAndParams( $tag, $text, $content, + $dummy_tags, $dummy_params, $uniq_prefix ); + } + /** * Strips and renders nowiki, pre, math, hiero * If $render is set, performs necessary rendering operations on plugins @@ -287,6 +347,8 @@ class Parser $pre_content = array(); $comment_content = array(); $ext_content = array(); + $ext_tags = array(); + $ext_params = array(); $gallery_content = array(); # Replace any instances of the placeholders @@ -362,13 +424,16 @@ class Parser # Extensions foreach ( $this->mTagHooks as $tag => $callback ) { - $ext_contents[$tag] = array(); - $text = Parser::extractTags( $tag, $text, $ext_content[$tag], $uniq_prefix ); + $ext_content[$tag] = array(); + $text = Parser::extractTagsAndParams( $tag, $text, $ext_content[$tag], + $ext_tags[$tag], $ext_params[$tag], $uniq_prefix ); foreach( $ext_content[$tag] as $marker => $content ) { + $full_tag = $ext_tags[$tag][$marker]; + $params = $ext_params[$tag][$marker]; if ( $render ) { - $ext_content[$tag][$marker] = $callback( $content ); + $ext_content[$tag][$marker] = $callback( $content, $params ); } else { - $ext_content[$tag][$marker] = "<$tag>$content"; + $ext_content[$tag][$marker] = "$full_tag$content"; } } } @@ -463,110 +528,108 @@ class Parser } /** - * Return allowed HTML attributes + * Interface with html tidy, used if $wgUseTidy = true. + * If tidy isn't able to correct the markup, the original will be + * returned in all its glory with a warning comment appended. * - * @access private - */ - function getHTMLattrs () { - $htmlattrs = array( # Allowed attributes--no scripting, etc. - 'title', 'align', 'lang', 'dir', 'width', 'height', - 'bgcolor', 'clear', /* BR */ 'noshade', /* HR */ - 'cite', /* BLOCKQUOTE, Q */ 'size', 'face', 'color', - /* FONT */ 'type', 'start', 'value', 'compact', - /* For various lists, mostly deprecated but safe */ - 'summary', 'width', 'border', 'frame', 'rules', - 'cellspacing', 'cellpadding', 'valign', 'char', - 'charoff', 'colgroup', 'col', 'span', 'abbr', 'axis', - 'headers', 'scope', 'rowspan', 'colspan', /* Tables */ - 'id', 'class', 'name', 'style' /* For CSS */ - ); - return $htmlattrs ; - } - - /** - * Remove non approved attributes and javascript in css + * Either the external tidy program or the in-process tidy extension + * will be used depending on availability. Override the default + * $wgTidyInternal setting to disable the internal if it's not working. * - * @access private + * @param string $text Hideous HTML input + * @return string Corrected HTML output + * @access public + * @static */ - function fixTagAttributes ( $t ) { - if ( trim ( $t ) == '' ) return '' ; # Saves runtime ;-) - $htmlattrs = $this->getHTMLattrs() ; - - # Strip non-approved attributes from the tag - $t = preg_replace( - '/(\\w+)(\\s*=\\s*([^\\s\">]+|\"[^\">]*\"))?/e', - "(in_array(strtolower(\"\$1\"),\$htmlattrs)?(\"\$1\".((\"x\$3\" != \"x\")?\"=\$3\":'')):'')", - $t); - - $t = str_replace ( '<>' , '' , $t ) ; # This should fix bug 980557 - - # Strip javascript "expression" from stylesheets. Brute force approach: - # If anythin offensive is found, all attributes of the HTML tag are dropped - - if( preg_match( - '/style\\s*=.*(expression|tps*:\/\/|url\\s*\().*/is', - wfMungeToUtf8( $t ) ) ) - { - $t=''; + function tidy( $text ) { + global $wgTidyInternal; + $wrappedtext = ''. +'test'.$text.''; + if( $wgTidyInternal ) { + $correctedtext = Parser::internalTidy( $wrappedtext ); + } else { + $correctedtext = Parser::externalTidy( $wrappedtext ); } - - return trim ( $t ) ; + if( is_null( $correctedtext ) ) { + wfDebug( "Tidy error detected!\n" ); + return $text . "\n\n"; + } + return $correctedtext; } - + /** - * interface with html tidy, used if $wgUseTidy = true + * Spawn an external HTML tidy process and get corrected markup back from it. * - * @access public + * @access private * @static */ - function tidy ( $text ) { + function externalTidy( $text ) { global $wgTidyConf, $wgTidyBin, $wgTidyOpts; - global $wgInputEncoding, $wgOutputEncoding; - $fname = 'Parser::tidy'; + $fname = 'Parser::externalTidy'; wfProfileIn( $fname ); $cleansource = ''; - $opts = ''; - switch(strtoupper($wgOutputEncoding)) { - case 'ISO-8859-1': - $opts .= ($wgInputEncoding == $wgOutputEncoding)? ' -latin1':' -raw'; - break; - case 'UTF-8': - $opts .= ($wgInputEncoding == $wgOutputEncoding)? ' -utf8':' -raw'; - break; - default: - $opts .= ' -raw'; - } + $opts = ' -utf8'; - $wrappedtext = ''. -'test'.$text.''; $descriptorspec = array( 0 => array('pipe', 'r'), 1 => array('pipe', 'w'), 2 => array('file', '/dev/null', 'a') ); + $pipes = array(); $process = proc_open("$wgTidyBin -config $wgTidyConf $wgTidyOpts$opts", $descriptorspec, $pipes); if (is_resource($process)) { - fwrite($pipes[0], $wrappedtext); + fwrite($pipes[0], $text); fclose($pipes[0]); while (!feof($pipes[1])) { $cleansource .= fgets($pipes[1], 1024); } fclose($pipes[1]); - $return_value = proc_close($process); + proc_close($process); } wfProfileOut( $fname ); if( $cleansource == '' && $text != '') { - wfDebug( "Tidy error detected!\n" ); - return $text . "\n\n"; + // Some kind of error happened, so we couldn't get the corrected text. + // Just give up; we'll use the source text and append a warning. + return null; } else { return $cleansource; } } + /** + * Use the HTML tidy PECL extension to use the tidy library in-process, + * saving the overhead of spawning a new process. Currently written to + * the PHP 4.3.x version of the extension, may not work on PHP 5. + * + * 'pear install tidy' should be able to compile the extension module. + * + * @access private + * @static + */ + function internalTidy( $text ) { + global $wgTidyConf; + $fname = 'Parser::internalTidy'; + wfProfileIn( $fname ); + + tidy_load_config( $wgTidyConf ); + tidy_set_encoding( 'utf8' ); + tidy_parse_string( $text ); + tidy_clean_repair(); + if( tidy_get_status() == 2 ) { + // 2 is magic number for fatal error + // http://www.php.net/manual/en/function.tidy-get-status.php + $cleansource = null; + } else { + $cleansource = tidy_get_output(); + } + wfProfileOut( $fname ); + return $cleansource; + } + /** * parse the wiki syntax used to render tables * @@ -588,9 +651,8 @@ class Parser $fc = substr ( $x , 0 , 1 ) ; if ( preg_match( '/^(:*)\{\|(.*)$/', $x, $matches ) ) { $indent_level = strlen( $matches[1] ); - $t[$k] = "\n" . - str_repeat( '
', $indent_level ) . - 'fixTagAttributes ( $matches[2] ) . '>' ; + $t[$k] = str_repeat( '
', $indent_level ) . + '' ; array_push ( $td , false ) ; array_push ( $ltd , '' ) ; array_push ( $tr , false ) ; @@ -598,7 +660,7 @@ class Parser } else if ( count ( $td ) == 0 ) { } # Don't do any of the following else if ( '|}' == substr ( $x , 0 , 2 ) ) { - $z = "
\n" ; + $z = "" . substr ( $x , 2); $l = array_pop ( $ltd ) ; if ( array_pop ( $tr ) ) $z = '' . $z ; if ( array_pop ( $td ) ) $z = '' . $z ; @@ -617,7 +679,7 @@ class Parser array_push ( $tr , false ) ; array_push ( $td , false ) ; array_push ( $ltd , '' ) ; - array_push ( $ltr , $this->fixTagAttributes ( $x ) ) ; + array_push ( $ltr , Sanitizer::fixTagAttributes ( $x, 'tr' ) ) ; } else if ( '|' == $fc || '!' == $fc || '|+' == substr ( $x , 0 , 2 ) ) { # Caption # $x is a table row @@ -637,7 +699,7 @@ class Parser if ( $fc != '+' ) { $tra = array_pop ( $ltr ) ; - if ( !array_pop ( $tr ) ) $z = '\n" ; + if ( !array_pop ( $tr ) ) $z = '\n" ; array_push ( $tr , true ) ; array_push ( $ltr , '' ) ; } @@ -659,7 +721,7 @@ class Parser } if ( count ( $y ) == 1 ) $y = "{$z}<{$l}>{$y[0]}" ; - else $y = $y = "{$z}<{$l} ".$this->fixTagAttributes($y[0]).">{$y[1]}" ; + else $y = $y = "{$z}<{$l}".Sanitizer::fixTagAttributes($y[0], $l).">{$y[1]}" ; $t[$k] .= $y ; array_push ( $td , true ) ; } @@ -675,7 +737,6 @@ class Parser } $t = implode ( "\n" , $t ) ; - # $t = $this->removeHTMLtags( $t ); wfProfileOut( $fname ); return $t ; } @@ -686,21 +747,22 @@ class Parser * * @access private */ - function internalParse( $text, $linestart, $args = array(), $isMain=true ) { + function internalParse( $text ) { global $wgContLang; - + $args = array(); + $isMain = true; $fname = 'Parser::internalParse'; wfProfileIn( $fname ); - $text = $this->removeHTMLtags( $text ); + $text = Sanitizer::removeHTMLtags( $text, array( &$this, 'replaceVariables' ) ); $text = $this->replaceVariables( $text, $args ); $text = preg_replace( '/(^|\n)-----*/', '\\1
', $text ); $text = $this->doHeadings( $text ); if($this->mOptions->getUseDynamicDates()) { - global $wgDateFormatter; - $text = $wgDateFormatter->reformat( $this->mOptions->getDateFormat(), $text ); + $df =& DateFormatter::getInstance(); + $text = $df->reformat( $this->mOptions->getDateFormat(), $text ); } $text = $this->doAllQuotes( $text ); $text = $this->replaceInternalLinks( $text ); @@ -725,11 +787,7 @@ class Parser * @access private */ function &doMagicLinks( &$text ) { - global $wgUseGeoMode; $text = $this->magicISBN( $text ); - if ( isset( $wgUseGeoMode ) && $wgUseGeoMode ) { - $text = $this->magicGEO( $text ); - } $text = $this->magicRFC( $text, 'RFC ', 'rfcurl' ); $text = $this->magicRFC( $text, 'PMID ', 'pubmedurl' ); return $text; @@ -957,12 +1015,11 @@ class Parser * @access private */ function replaceExternalLinks( $text ) { + global $wgContLang; $fname = 'Parser::replaceExternalLinks'; wfProfileIn( $fname ); $sk =& $this->mOptions->getSkin(); - global $wgContLang; - $linktrail = $wgContLang->linkTrail(); $bits = preg_split( EXT_LINK_BRACKETED, $text, -1, PREG_SPLIT_DELIM_CAPTURE ); @@ -985,53 +1042,50 @@ class Parser # If the link text is an image URL, replace it with an tag # This happened by accident in the original parser, but some people used it extensively - $img = $this->maybeMakeImageLink( $text ); + $img = $this->maybeMakeExternalImage( $text ); if ( $img !== false ) { $text = $img; } $dtrail = ''; + # Set linktype for CSS - if URL==text, link is essentially free + $linktype = ($text == $url) ? 'free' : 'text'; + # No link text, e.g. [http://domain.tld/some.link] if ( $text == '' ) { # Autonumber if allowed if ( strpos( HTTP_PROTOCOLS, $protocol ) !== false ) { $text = '[' . ++$this->mAutonumber . ']'; + $linktype = 'autonumber'; } else { # Otherwise just use the URL $text = htmlspecialchars( $url ); + $linktype = 'free'; } } else { # Have link text, e.g. [http://domain.tld/some.link text]s # Check for trail - if ( preg_match( $linktrail, $trail, $m2 ) ) { - $dtrail = $m2[1]; - $trail = $m2[2]; - } + list( $dtrail, $trail ) = Linker::splitTrail( $trail ); } - $encUrl = htmlspecialchars( $url ); - # Bit in parentheses showing the URL for the printable version - if( $url == $text || preg_match( "!$protocol://" . preg_quote( $text, '/' ) . "/?$!", $url ) ) { - $paren = ''; - } else { - # Expand the URL for printable version - if ( ! $sk->suppressUrlExpansion() ) { - $paren = " (" . htmlspecialchars ( $encUrl ) . ")"; - } else { - $paren = ''; - } - } + $text = $wgContLang->markNoConversion($text); + + # Replace & from obsolete syntax with &. + # All HTML entities will be escaped by makeExternalLink() + # or maybeMakeExternalImage() + $url = str_replace( '&', '&', $url ); # Process the trail (i.e. everything after this link up until start of the next link), # replacing any non-bracketed links $trail = $this->replaceFreeExternalLinks( $trail ); + # Use the encoded URL # This means that users can paste URLs directly into the text # Funny characters like ö aren't valid in URLs anyway # This was changed in August 2004 - $s .= $sk->makeExternalLink( $url, $text, false ) . $dtrail. $paren . $trail; + $s .= $sk->makeExternalLink( $url, $text, false, $linktype ) . $dtrail . $trail; } wfProfileOut( $fname ); @@ -1043,6 +1097,7 @@ class Parser * @access private */ function replaceFreeExternalLinks( $text ) { + global $wgContLang; $fname = 'Parser::replaceFreeExternalLinks'; wfProfileIn( $fname ); @@ -1084,14 +1139,14 @@ class Parser # Replace & from obsolete syntax with &. # All HTML entities will be escaped by makeExternalLink() - # or maybeMakeImageLink() + # or maybeMakeExternalImage() $url = str_replace( '&', '&', $url ); # Is this an external image? - $text = $this->maybeMakeImageLink( $url ); + $text = $this->maybeMakeExternalImage( $url ); if ( $text === false ) { # Not an image, make a link - $text = $sk->makeExternalLink( $url, $url ); + $text = $sk->makeExternalLink( $url, $wgContLang->markNoConversion($url), true, 'free' ); } $s .= $text . $trail; } else { @@ -1106,13 +1161,13 @@ class Parser * make an image if it's allowed * @access private */ - function maybeMakeImageLink( $url ) { + function maybeMakeExternalImage( $url ) { $sk =& $this->mOptions->getSkin(); $text = false; if ( $this->mOptions->getAllowExternalImages() ) { if ( preg_match( EXT_IMAGE_REGEX, $url ) ) { # Image found - $text = $sk->makeImage( htmlspecialchars( $url ) ); + $text = $sk->makeExternalImage( htmlspecialchars( $url ) ); } } return $text; @@ -1123,10 +1178,8 @@ class Parser * * @access private */ - function replaceInternalLinks( $s ) { - global $wgLang, $wgContLang, $wgLinkCache; - global $wgDisableLangConversion; + global $wgContLang, $wgLinkCache; static $fname = 'Parser::replaceInternalLinks' ; wfProfileIn( $fname ); @@ -1137,12 +1190,6 @@ class Parser if ( !$tc ) { $tc = Title::legalChars() . '#%'; } $sk =& $this->mOptions->getSkin(); - global $wgUseOldExistenceCheck; - # "Post-parse link colour check" works only on wiki text since it's now - # in Parser. Enable it, then disable it when we're done. - $saveParseColour = $sk->postParseLinkColour( !$wgUseOldExistenceCheck ); - - $redirect = MagicWord::get ( MAG_REDIRECT ) ; #split the entire text string on occurences of [[ $a = explode( '[[', ' ' . $s ); @@ -1152,7 +1199,7 @@ class Parser # Match a link having the form [[namespace:link|alternate]]trail static $e1 = FALSE; - if ( !$e1 ) { $e1 = "/^([{$tc}]+)(?:\\|([^]]+))?]](.*)\$/sD"; } + if ( !$e1 ) { $e1 = "/^([{$tc}]+)(?:\\|(.+?))?]](.*)\$/sD"; } # Match cases where there is no "]]", which might still be images static $e1_img = FALSE; if ( !$e1_img ) { $e1_img = "/^([{$tc}]+)\\|(.*)\$/sD"; } @@ -1162,7 +1209,10 @@ class Parser $useLinkPrefixExtension = $wgContLang->linkPrefixExtension(); - $nottalk = !Namespace::isTalk( $this->mTitle->getNamespace() ); + if( is_null( $this->mTitle ) ) { + wfDebugDieBacktrace( 'nooo' ); + } + $nottalk = !$this->mTitle->isTalkPage(); if ( $useLinkPrefixExtension ) { if ( preg_match( $e2, $s, $m ) ) { @@ -1204,6 +1254,18 @@ class Parser if ( preg_match( $e1, $line, $m ) ) { # page with normal text or alt $text = $m[2]; + # If we get a ] at the beginning of $m[3] that means we have a link that's something like: + # [[Image:Foo.jpg|[http://example.com desc]]] <- having three ] in a row fucks up, + # the real problem is with the $e1 regex + # See bug 1300. + # + # Still some problems for cases where the ] is meant to be outside punctuation, + # and no image is in sight. See bug 2095. + # + if( $text !== '' && preg_match( "/^\](.*)/s", $m[3], $n ) ) { + $text .= ']'; # so that replaceExternalLinks($text) works later + $m[3] = $n[1]; + } # fix up urlencoded title texts if(preg_match('/%/', $m[1] )) $m[1] = urldecode($m[1]); $trail = $m[3]; @@ -1300,37 +1362,38 @@ class Parser # Interwikis if( $iw && $this->mOptions->getInterwikiMagic() && $nottalk && $wgContLang->getLanguageName( $iw ) ) { array_push( $this->mOutput->mLanguageLinks, $nt->getFullText() ); - $tmp = $prefix . $trail ; - $s .= (trim($tmp) == '')? '': $tmp; + $s = rtrim($s . "\n"); + $s .= trim($prefix . $trail, "\n") == '' ? '': $prefix . $trail; continue; } if ( $ns == NS_IMAGE ) { wfProfileIn( "$fname-image" ); - - # recursively parse links inside the image caption - # actually, this will parse them in any other parameters, too, - # but it might be hard to fix that, and it doesn't matter ATM - $text = $this->replaceExternalLinks($text); - $text = $this->replaceInternalLinks($text); - - # replace the image with a link-holder so that replaceExternalLinks() can't mess with it - $s .= $prefix . $this->insertStripItem( $sk->makeImageLinkObj( $nt, $text ), $this->mStripState ) . $trail; - $wgLinkCache->addImageLinkObj( $nt ); - + if ( !wfIsBadImage( $nt->getDBkey() ) ) { + # recursively parse links inside the image caption + # actually, this will parse them in any other parameters, too, + # but it might be hard to fix that, and it doesn't matter ATM + $text = $this->replaceExternalLinks($text); + $text = $this->replaceInternalLinks($text); + + # cloak any absolute URLs inside the image markup, so replaceExternalLinks() won't touch them + $s .= $prefix . str_replace('http://', 'http-noparse://', $this->makeImage( $nt, $text ) ) . $trail; + $wgLinkCache->addImageLinkObj( $nt ); + + wfProfileOut( "$fname-image" ); + continue; + } wfProfileOut( "$fname-image" ); - continue; + } if ( $ns == NS_CATEGORY ) { wfProfileIn( "$fname-category" ); - $t = $nt->getText(); + $t = $wgContLang->convert($nt->getText()); + $s = rtrim($s . "\n"); # bug 87 $wgLinkCache->suspend(); # Don't save in links/brokenlinks - $pPLC=$sk->postParseLinkColour(); - $sk->postParseLinkColour( false ); $t = $sk->makeLinkObj( $nt, $t, '', '' , $prefix ); - $sk->postParseLinkColour( $pPLC ); $wgLinkCache->resume(); if ( $wasblank ) { @@ -1342,9 +1405,15 @@ class Parser } else { $sortkey = $text; } + $sortkey = $wgContLang->convertCategoryKey( $sortkey ); $wgLinkCache->addCategoryLinkObj( $nt, $sortkey ); $this->mOutput->addCategoryLink( $t ); - $s .= $prefix . $trail ; + + /** + * Strip the whitespace Category links produce, see bug 87 + * @todo We might want to use trim($tmp, "\n") here. + */ + $s .= trim($prefix . $trail, "\n") == '' ? '': $prefix . $trail; wfProfileOut( "$fname-category" ); continue; @@ -1367,13 +1436,59 @@ class Parser $s .= $prefix . $sk->makeKnownLinkObj( $nt, $text, '', $trail ); continue; } - $s .= $sk->makeLinkObj( $nt, $text, '', $trail, $prefix ); + if( $nt->isLocal() && $nt->isAlwaysKnown() ) { + /** + * Skip lookups for special pages and self-links. + * External interwiki links are not included here because + * the HTTP urls would break output in the next parse step; + * they will have placeholders kept. + */ + $s .= $sk->makeKnownLinkObj( $nt, $text, '', $trail, $prefix ); + } else { + /** + * Add a link placeholder + * Later, this will be replaced by a real link, after the existence or + * non-existence of all the links is known + */ + $s .= $this->makeLinkHolder( $nt, $text, '', $trail, $prefix ); + } } - $sk->postParseLinkColour( $saveParseColour ); wfProfileOut( $fname ); return $s; } + /** + * Make a link placeholder. The text returned can be later resolved to a real link with + * replaceLinkHolders(). This is done for two reasons: firstly to avoid further + * parsing of interwiki links, and secondly to allow all extistence checks and + * article length checks (for stub links) to be bundled into a single query. + * + */ + function makeLinkHolder( &$nt, $text = '', $query = '', $trail = '', $prefix = '' ) { + if ( ! is_object($nt) ) { + # Fail gracefully + $retVal = "{$prefix}{$text}{$trail}"; + } else { + # Separate the link trail from the rest of the link + list( $inside, $trail ) = Linker::splitTrail( $trail ); + + if ( $nt->isExternal() ) { + $nr = array_push( $this->mInterwikiLinkHolders['texts'], $prefix.$text.$inside ); + $this->mInterwikiLinkHolders['titles'][] =& $nt; + $retVal = '{$trail}"; + } else { + $nr = array_push( $this->mLinkHolders['namespaces'], $nt->getNamespace() ); + $this->mLinkHolders['dbkeys'][] = $nt->getDBkey(); + $this->mLinkHolders['queries'][] = $query; + $this->mLinkHolders['texts'][] = $prefix.$text.$inside; + $this->mLinkHolders['titles'][] =& $nt; + + $retVal = '{$trail}"; + } + } + return $retVal; + } + /** * Return true if subpage links should be expanded on this page. * @return bool @@ -1545,7 +1660,7 @@ class Parser # $textLines = explode( "\n", $text ); - $lastPrefix = $output = $lastLine = ''; + $lastPrefix = $output = ''; $this->mDTopen = $inBlockElem = false; $prefixLength = 0; $paragraphStack = false; @@ -1584,6 +1699,7 @@ class Parser # ; title : definition text # So we check for : in the remainder text to split up the # title and definition, without b0rking links. + $term = $t2 = ''; if ($this->findColonNoLinks($t, $term, $t2) !== false) { $t = $t2; $output .= $term . $this->nextItem( ':' ); @@ -1738,7 +1854,7 @@ class Parser * @access private */ function getVariableValue( $index ) { - global $wgContLang, $wgSitename, $wgServer; + global $wgContLang, $wgSitename, $wgServer, $wgArticle; /** * Some of these require message or data lookups and can be @@ -1754,19 +1870,23 @@ class Parser return $varCache[$index] = $wgContLang->getMonthName( date('n') ); case MAG_CURRENTMONTHNAMEGEN: return $varCache[$index] = $wgContLang->getMonthNameGen( date('n') ); + case MAG_CURRENTMONTHABBREV: + return $varCache[$index] = $wgContLang->getMonthAbbreviation( date('n') ); case MAG_CURRENTDAY: return $varCache[$index] = $wgContLang->formatNum( date('j') ); case MAG_PAGENAME: return $this->mTitle->getText(); case MAG_PAGENAMEE: return $this->mTitle->getPartialURL(); + case MAG_REVISIONID: + return $wgArticle->getRevIdFetched(); case MAG_NAMESPACE: # return Namespace::getCanonicalName($this->mTitle->getNamespace()); return $wgContLang->getNsText($this->mTitle->getNamespace()); # Patch by Dori case MAG_CURRENTDAYNAME: return $varCache[$index] = $wgContLang->getWeekdayName( date('w')+1 ); case MAG_CURRENTYEAR: - return $varCache[$index] = $wgContLang->formatNum( date( 'Y' ) ); + return $varCache[$index] = $wgContLang->formatNum( date( 'Y' ), true ); case MAG_CURRENTTIME: return $varCache[$index] = $wgContLang->time( wfTimestampNow(), false ); case MAG_CURRENTWEEK: @@ -1796,7 +1916,7 @@ class Parser $this->mVariables = array(); foreach ( $wgVariableIDs as $id ) { $mw =& MagicWord::get( $id ); - $mw->addToArray( $this->mVariables, $this->getVariableValue( $id ) ); + $mw->addToArray( $this->mVariables, $id ); } wfProfileOut( $fname ); } @@ -1816,7 +1936,6 @@ class Parser * @access private */ function replaceVariables( $text, $args = array() ) { - global $wgLang, $wgScript, $wgArticlePath; # Prevent too big inclusions if( strlen( $text ) > MAX_INCLUDE_SIZE ) { @@ -1854,6 +1973,7 @@ class Parser */ function variableSubstitution( $matches ) { $fname = 'parser::variableSubstitution'; + $varname = $matches[1]; wfProfileIn( $fname ); if ( !$this->mVariables ) { $this->initialiseVariables(); @@ -1862,14 +1982,15 @@ class Parser if ( $this->mOutputType == OT_WIKI ) { # Do only magic variables prefixed by SUBST $mwSubst =& MagicWord::get( MAG_SUBST ); - if (!$mwSubst->matchStartAndRemove( $matches[1] )) + if (!$mwSubst->matchStartAndRemove( $varname )) $skip = true; # Note that if we don't substitute the variable below, # we don't remove the {{subst:}} magic word, in case # it is a template rather than a magic variable. } - if ( !$skip && array_key_exists( $matches[1], $this->mVariables ) ) { - $text = $this->mVariables[$matches[1]]; + if ( !$skip && array_key_exists( $varname, $this->mVariables ) ) { + $id = $this->mVariables[$varname]; + $text = $this->getVariableValue( $id ); $this->mOutput->mContainsOldMagic = true; } else { $text = $matches[0]; @@ -1890,7 +2011,7 @@ class Parser # merged with the next arg because the '|' character between belongs # to the link syntax and not the template parameter syntax. $argc = count($args); - $i = 0; + for ( $i = 0; $i < $argc-1; $i++ ) { if ( substr_count ( $args[$i], '[[' ) != substr_count ( $args[$i], ']]' ) ) { $args[$i] .= '|'.$args[$i+1]; @@ -2039,20 +2160,26 @@ class Parser # Did we encounter this template already? If yes, it is in the cache # and we need to check for loops. if ( !$found && isset( $this->mTemplates[$part1] ) ) { - # set $text to cached message. - $text = $linestart . $this->mTemplates[$part1]; $found = true; # Infinite loop test if ( isset( $this->mTemplatePath[$part1] ) ) { $noparse = true; $found = true; - $text .= ''; + $text = $linestart . + "\{\{$part1}}" . + ''; + wfDebug( "$fname: template loop broken at '$part1'\n" ); + } else { + # set $text to cached message. + $text = $linestart . $this->mTemplates[$part1]; } } # Load from database - $itcamefromthedatabase = false; + $replaceHeadings = false; + $isHTML = false; + $lastPathLevel = $this->mTemplatePath; if ( !$found ) { $ns = NS_TEMPLATE; $part1 = $this->maybeDoSubpageLink( $part1, $subpage='' ); @@ -2064,24 +2191,37 @@ class Parser # Check for excessive inclusion $dbk = $title->getPrefixedDBkey(); if ( $this->incrementIncludeCount( $dbk ) ) { - # This should never be reached. - $article = new Article( $title ); - $articleContent = $article->getContentWithoutUsingSoManyDamnGlobals(); - if ( $articleContent !== false ) { - $found = true; - $text = $linestart . $articleContent; - $itcamefromthedatabase = true; + if ( $title->getNamespace() == NS_SPECIAL && $this->mOptions->getAllowSpecialInclusion() ) { + # Capture special page output + $text = SpecialPage::capturePath( $title ); + if ( $text && !is_object( $text ) ) { + $found = true; + $noparse = true; + $isHTML = true; + $this->mOutput->setCacheTime( -1 ); + } + } else { + $article = new Article( $title ); + $articleContent = $article->getContentWithoutUsingSoManyDamnGlobals(); + if ( $articleContent !== false ) { + $found = true; + $text = $articleContent; + $replaceHeadings = true; + } } } # If the title is valid but undisplayable, make a link to it if ( $this->mOutputType == OT_HTML && !$found ) { - $text = $linestart . '[['.$title->getPrefixedText().']]'; + $text = '[['.$title->getPrefixedText().']]'; $found = true; } # Template cache array insertion - $this->mTemplates[$part1] = $text; + if( $found ) { + $this->mTemplates[$part1] = $text; + $text = $linestart . $text; + } } } @@ -2112,8 +2252,10 @@ class Parser # Add a new element to the templace recursion path $this->mTemplatePath[$part1] = 1; - $text = $this->strip( $text, $this->mStripState ); - $text = $this->removeHTMLtags( $text ); + if( $this->mOutputType == OT_HTML ) { + $text = $this->strip( $text, $this->mStripState ); + $text = Sanitizer::removeHTMLtags( $text, array( &$this, 'replaceVariables' ), $assocArgs ); + } $text = $this->replaceVariables( $text, $assocArgs ); # Resume the link cache and register the inclusion as a link @@ -2127,43 +2269,50 @@ class Parser $text = "\n" . $text; } } - - # Empties the template path - $this->mTemplatePath = array(); + # Prune lower levels off the recursion check path + $this->mTemplatePath = $lastPathLevel; + if ( !$found ) { wfProfileOut( $fname ); return $matches[0]; } else { - # replace ==section headers== - # XXX this needs to go away once we have a better parser. - if ( $this->mOutputType != OT_WIKI && $itcamefromthedatabase ) { - if( !is_null( $title ) ) - $encodedname = base64_encode($title->getPrefixedDBkey()); - else - $encodedname = base64_encode(""); - $m = preg_split('/(^={1,6}.*?={1,6}\s*?$)/m', $text, -1, - PREG_SPLIT_DELIM_CAPTURE); - $text = ''; - $nsec = 0; - for( $i = 0; $i < count($m); $i += 2 ) { - $text .= $m[$i]; - if (!isset($m[$i + 1]) || $m[$i + 1] == "") continue; - $hl = $m[$i + 1]; - if( strstr($hl, "" . $m2[3]; + + $nsec++; } - preg_match('/^(={1,6})(.*?)(={1,6})\s*?$/m', $hl, $m2); - $text .= $m2[1] . $m2[2] . "" . $m2[3]; - - $nsec++; } } } - - # Empties the template path - $this->mTemplatePath = array(); + + # Prune lower levels off the recursion check path + $this->mTemplatePath = $lastPathLevel; if ( !$found ) { wfProfileOut( $fname ); @@ -2205,171 +2354,6 @@ class Parser } } - - /** - * Cleans up HTML, removes dangerous tags and attributes, and - * removes HTML comments - * @access private - */ - function removeHTMLtags( $text ) { - global $wgUseTidy, $wgUserHtml; - $fname = 'Parser::removeHTMLtags'; - wfProfileIn( $fname ); - - if( $wgUserHtml ) { - $htmlpairs = array( # Tags that must be closed - 'b', 'del', 'i', 'ins', 'u', 'font', 'big', 'small', 'sub', 'sup', 'h1', - 'h2', 'h3', 'h4', 'h5', 'h6', 'cite', 'code', 'em', 's', - 'strike', 'strong', 'tt', 'var', 'div', 'center', - 'blockquote', 'ol', 'ul', 'dl', 'table', 'caption', 'pre', - 'ruby', 'rt' , 'rb' , 'rp', 'p', 'span' - ); - $htmlsingle = array( - 'br', 'hr', 'li', 'dt', 'dd' - ); - $htmlnest = array( # Tags that can be nested--?? - 'table', 'tr', 'td', 'th', 'div', 'blockquote', 'ol', 'ul', - 'dl', 'font', 'big', 'small', 'sub', 'sup', 'span' - ); - $tabletags = array( # Can only appear inside table - 'td', 'th', 'tr' - ); - } else { - $htmlpairs = array(); - $htmlsingle = array(); - $htmlnest = array(); - $tabletags = array(); - } - - $htmlsingle = array_merge( $tabletags, $htmlsingle ); - $htmlelements = array_merge( $htmlsingle, $htmlpairs ); - - $htmlattrs = $this->getHTMLattrs () ; - - # Remove HTML comments - $text = $this->removeHTMLcomments( $text ); - - $bits = explode( '<', $text ); - $text = array_shift( $bits ); - if(!$wgUseTidy) { - $tagstack = array(); $tablestack = array(); - foreach ( $bits as $x ) { - $prev = error_reporting( E_ALL & ~( E_NOTICE | E_WARNING ) ); - preg_match( '/^(\\/?)(\\w+)([^>]*)(\\/{0,1}>)([^<]*)$/', - $x, $regs ); - list( $qbar, $slash, $t, $params, $brace, $rest ) = $regs; - error_reporting( $prev ); - - $badtag = 0 ; - if ( in_array( $t = strtolower( $t ), $htmlelements ) ) { - # Check our stack - if ( $slash ) { - # Closing a tag... - if ( ! in_array( $t, $htmlsingle ) && - ( $ot = @array_pop( $tagstack ) ) != $t ) { - @array_push( $tagstack, $ot ); - $badtag = 1; - } else { - if ( $t == 'table' ) { - $tagstack = array_pop( $tablestack ); - } - $newparams = ''; - } - } else { - # Keep track for later - if ( in_array( $t, $tabletags ) && - ! in_array( 'table', $tagstack ) ) { - $badtag = 1; - } else if ( in_array( $t, $tagstack ) && - ! in_array ( $t , $htmlnest ) ) { - $badtag = 1 ; - } else if ( ! in_array( $t, $htmlsingle ) ) { - if ( $t == 'table' ) { - array_push( $tablestack, $tagstack ); - $tagstack = array(); - } - array_push( $tagstack, $t ); - } - # Strip non-approved attributes from the tag - $newparams = $this->fixTagAttributes($params); - - } - if ( ! $badtag ) { - $rest = str_replace( '>', '>', $rest ); - $text .= "<$slash$t $newparams$brace$rest"; - continue; - } - } - $text .= '<' . str_replace( '>', '>', $x); - } - # Close off any remaining tags - while ( is_array( $tagstack ) && ($t = array_pop( $tagstack )) ) { - $text .= "\n"; - if ( $t == 'table' ) { $tagstack = array_pop( $tablestack ); } - } - } else { - # this might be possible using tidy itself - foreach ( $bits as $x ) { - preg_match( '/^(\\/?)(\\w+)([^>]*)(\\/{0,1}>)([^<]*)$/', - $x, $regs ); - @list( $qbar, $slash, $t, $params, $brace, $rest ) = $regs; - if ( in_array( $t = strtolower( $t ), $htmlelements ) ) { - $newparams = $this->fixTagAttributes($params); - $rest = str_replace( '>', '>', $rest ); - $text .= "<$slash$t $newparams$brace$rest"; - } else { - $text .= '<' . str_replace( '>', '>', $x); - } - } - } - wfProfileOut( $fname ); - return $text; - } - - /** - * Remove '', and everything between. - * To avoid leaving blank lines, when a comment is both preceded - * and followed by a newline (ignoring spaces), trim leading and - * trailing spaces and one of the newlines. - * - * @access private - */ - function removeHTMLcomments( $text ) { - $fname='Parser::removeHTMLcomments'; - wfProfileIn( $fname ); - while (($start = strpos($text, '', $start + 4); - if ($end === false) { - # Unterminated comment; bail out - break; - } - - $end += 3; - - # Trim space and newline if the comment is both - # preceded and followed by a newline - $spaceStart = max($start - 1, 0); - $spaceLen = $end - $spaceStart; - while (substr($text, $spaceStart, 1) === ' ' && $spaceStart > 0) { - $spaceStart--; - $spaceLen++; - } - while (substr($text, $spaceStart + $spaceLen, 1) === ' ') - $spaceLen++; - if (substr($text, $spaceStart, 1) === "\n" and substr($text, $spaceStart + $spaceLen, 1) === "\n") { - # Remove the comment, leading and trailing - # spaces, and leave only one newline. - $text = substr_replace($text, "\n", $spaceStart, $spaceLen + 1); - } - else { - # Remove just the comment. - $text = substr_replace($text, '', $start, $end - $start); - } - } - wfProfileOut( $fname ); - return $text; - } - /** * This function accomplishes several tasks: * 1) Auto-number headings if that option is enabled @@ -2379,20 +2363,21 @@ class Parser * * It loops through all headlines, collects the necessary data, then splits up the * string and re-inserts the newly formatted headlines. + * + * @param string $text + * @param boolean $isMain * @access private */ - /* private */ function formatHeadings( $text, $isMain=true ) { - global $wgInputEncoding, $wgMaxTocLevel, $wgContLang, $wgLinkHolders; + function formatHeadings( $text, $isMain=true ) { + global $wgMaxTocLevel, $wgContLang, $wgLinkHolders, $wgInterwikiLinkHolders; $doNumberHeadings = $this->mOptions->getNumberHeadings(); - $doShowToc = $this->mOptions->getShowToc(); + $doShowToc = true; $forceTocHere = false; if( !$this->mTitle->userCanEdit() ) { $showEditLink = 0; - $rightClickHack = 0; } else { $showEditLink = $this->mOptions->getEditSection(); - $rightClickHack = $this->mOptions->getEditSectionOnRightClick(); } # Inhibit editsection links if requested in the page @@ -2404,13 +2389,7 @@ class Parser # do not add TOC $mw =& MagicWord::get( MAG_NOTOC ); if( $mw->matchAndRemove( $text ) ) { - $doShowToc = 0; - } - - # never add the TOC to the Main Page. This is an entry page that should not - # be more than 1-2 screens large anyway - if( $this->mTitle->getPrefixedText() == wfMsg('mainpage') ) { - $doShowToc = 0; + $doShowToc = false; } # Get all headlines for numbering them and adding funky stuff like [edit] @@ -2419,25 +2398,29 @@ class Parser # if there are fewer than 4 headlines in the article, do not show TOC if( $numMatches < 4 ) { - $doShowToc = 0; + $doShowToc = false; } # if the string __TOC__ (not case-sensitive) occurs in the HTML, # override above conditions and always show TOC at that place + $mw =& MagicWord::get( MAG_TOC ); - if ($mw->match( $text ) ) { - $doShowToc = 1; + if($mw->match( $text ) ) { + $doShowToc = true; $forceTocHere = true; } else { # if the string __FORCETOC__ (not case-sensitive) occurs in the HTML, # override above conditions and always show TOC above first header $mw =& MagicWord::get( MAG_FORCETOC ); if ($mw->matchAndRemove( $text ) ) { - $doShowToc = 1; + $doShowToc = true; } } - + # Never ever show TOC if no headers + if( $numMatches < 1 ) { + $doShowToc = false; + } # We need this to perform operations on the HTML $sk =& $this->mOptions->getSkin(); @@ -2448,17 +2431,22 @@ class Parser # Ugh .. the TOC should have neat indentation levels which can be # passed to the skin functions. These are determined here - $toclevel = 0; $toc = ''; $full = ''; $head = array(); $sublevelCount = array(); + $levelCount = array(); + $toclevel = 0; $level = 0; $prevlevel = 0; + $toclevel = 0; + $prevtoclevel = 0; + foreach( $matches[3] as $headline ) { $istemplate = 0; - $templatetitle = ""; + $templatetitle = ''; $templatesection = 0; + $numbering = ''; if (preg_match("//", $headline, $mat)) { $istemplate = 1; @@ -2467,28 +2455,54 @@ class Parser $headline = preg_replace("//", "", $headline); } - $numbering = ''; - if( $level ) { + if( $toclevel ) { $prevlevel = $level; + $prevtoclevel = $toclevel; } $level = $matches[1][$headlineCount]; - if( ( $doNumberHeadings || $doShowToc ) && $prevlevel && $level > $prevlevel ) { - # reset when we enter a new level - $sublevelCount[$level] = 0; - $toc .= $sk->tocIndent( $level - $prevlevel ); - $toclevel += $level - $prevlevel; - } - if( ( $doNumberHeadings || $doShowToc ) && $level < $prevlevel ) { - # reset when we step back a level - $sublevelCount[$level+1]=0; - $toc .= $sk->tocUnindent( $prevlevel - $level ); - $toclevel -= $prevlevel - $level; - } - # count number of headlines for each level - @$sublevelCount[$level]++; + if( $doNumberHeadings || $doShowToc ) { + + if ( $level > $prevlevel ) { + # Increase TOC level + $toclevel++; + $sublevelCount[$toclevel] = 0; + $toc .= $sk->tocIndent(); + } + elseif ( $level < $prevlevel && $toclevel > 1 ) { + # Decrease TOC level, find level to jump to + + if ( $toclevel == 2 && $level <= $levelCount[1] ) { + # Can only go down to level 1 + $toclevel = 1; + } else { + for ($i = $toclevel; $i > 0; $i--) { + if ( $levelCount[$i] == $level ) { + # Found last matching level + $toclevel = $i; + break; + } + elseif ( $levelCount[$i] < $level ) { + # Found first matching level below current level + $toclevel = $i + 1; + break; + } + } + } + + $toc .= $sk->tocUnindent( $prevtoclevel - $toclevel ); + } + else { + # No change in level, end TOC line + $toc .= $sk->tocLineEnd(); + } + + $levelCount[$toclevel] = $level; + + # count number of headlines for each level + @$sublevelCount[$toclevel]++; $dot = 0; - for( $i = 1; $i <= $level; $i++ ) { + for( $i = 1; $i <= $toclevel; $i++ ) { if( !empty( $sublevelCount[$i] ) ) { if( $dot ) { $numbering .= '.'; @@ -2502,41 +2516,38 @@ class Parser # The canonized header is a version of the header text safe to use for links # Avoid insertion of weird stuff like by expanding the relevant sections $canonized_headline = $this->unstrip( $headline, $this->mStripState ); - $canonized_headline = $this->unstripNoWiki( $headline, $this->mStripState ); + $canonized_headline = $this->unstripNoWiki( $canonized_headline, $this->mStripState ); # Remove link placeholders by the link text. # # turns into # link text with suffix $canonized_headline = preg_replace( '//e', - "\$wgLinkHolders['texts'][\$1]", + "\$this->mLinkHolders['texts'][\$1]", + $canonized_headline ); + $canonized_headline = preg_replace( '//e', + "\$this->mInterwikiLinkHolders['texts'][\$1]", $canonized_headline ); # strip out HTML $canonized_headline = preg_replace( '/<.*?' . '>/','',$canonized_headline ); $tocline = trim( $canonized_headline ); - $canonized_headline = urlencode( do_html_entity_decode( str_replace(' ', '_', $tocline), ENT_COMPAT, $wgInputEncoding ) ); + $canonized_headline = urlencode( Sanitizer::decodeCharReferences( str_replace(' ', '_', $tocline) ) ); $replacearray = array( '%3A' => ':', '%' => '.' ); $canonized_headline = str_replace(array_keys($replacearray),array_values($replacearray),$canonized_headline); - $refer[$headlineCount] = $canonized_headline; + $refers[$headlineCount] = $canonized_headline; # count how many in assoc. array so we can track dupes in anchors @$refers[$canonized_headline]++; $refcount[$headlineCount]=$refers[$canonized_headline]; - # Prepend the number to the heading text - - if( $doNumberHeadings || $doShowToc ) { - $tocline = $numbering . ' ' . $tocline; - - # Don't number the heading if it is the only one (looks silly) - if( $doNumberHeadings && count( $matches[3] ) > 1) { - # the two are different if the line contains a link - $headline=$numbering . ' ' . $headline; - } + # Don't number the heading if it is the only one (looks silly) + if( $doNumberHeadings && count( $matches[3] ) > 1) { + # the two are different if the line contains a link + $headline=$numbering . ' ' . $headline; } # Create the anchor for linking from the TOC to the section @@ -2545,7 +2556,7 @@ class Parser $anchor .= '_' . $refcount[$headlineCount]; } if( $doShowToc && ( !isset($wgMaxTocLevel) || $toclevel<$wgMaxTocLevel ) ) { - $toc .= $sk->tocLine($anchor,$tocline,$toclevel); + $toc .= $sk->tocLine($anchor, $tocline, $numbering, $toclevel); } if( $showEditLink && ( !$istemplate || $templatetitle !== "" ) ) { if ( empty( $head[$headlineCount] ) ) { @@ -2557,14 +2568,6 @@ class Parser $head[$headlineCount] .= $sk->editSectionLink($this->mTitle, $sectionCount+1); } - # Add the edit section span - if( $rightClickHack ) { - if( $istemplate ) - $headline = $sk->editSectionScriptForOther($templatetitle, $templatesection, $headline); - else - $headline = $sk->editSectionScript($this->mTitle, $sectionCount+1,$headline); - } - # give headline the correct tag @$head[$headlineCount] .= "'; @@ -2574,9 +2577,8 @@ class Parser } if( $doShowToc ) { - $toclines = $headlineCount; - $toc .= $sk->tocUnindent( $toclevel ); - $toc = $sk->tocTable( $toc ); + $toc .= $sk->tocUnindent( $toclevel - 1 ); + $toc = $sk->tocList( $toc ); } # split up and insert constructed headlines @@ -2617,7 +2619,6 @@ class Parser * @access private */ function magicISBN( $text ) { - global $wgLang; $fname = 'Parser::magicISBN'; wfProfileIn( $fname ); @@ -2627,7 +2628,7 @@ class Parser return $text; } $text = substr( array_shift( $a ), 1); - $valid = '0123456789-ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + $valid = '0123456789-Xx'; foreach ( $a as $x ) { $isbn = $blank = '' ; @@ -2645,6 +2646,7 @@ class Parser } $num = str_replace( '-', '', $isbn ); $num = str_replace( ' ', '', $num ); + $num = str_replace( 'x', 'X', $num ); if ( '' == $num ) { $text .= "ISBN $blank$x"; @@ -2660,64 +2662,16 @@ class Parser return $text; } - /** - * Return an HTML link for the "GEO ..." text - * @access private - */ - function magicGEO( $text ) { - global $wgLang, $wgUseGeoMode; - $fname = 'Parser::magicGEO'; - wfProfileIn( $fname ); - - # These next five lines are only for the ~35000 U.S. Census Rambot pages... - $directions = array ( 'N' => 'North' , 'S' => 'South' , 'E' => 'East' , 'W' => 'West' ) ; - $text = preg_replace ( "/(\d+)°(\d+)'(\d+)\" {$directions['N']}, (\d+)°(\d+)'(\d+)\" {$directions['W']}/" , "(GEO +\$1.\$2.\$3:-\$4.\$5.\$6)" , $text ) ; - $text = preg_replace ( "/(\d+)°(\d+)'(\d+)\" {$directions['N']}, (\d+)°(\d+)'(\d+)\" {$directions['E']}/" , "(GEO +\$1.\$2.\$3:+\$4.\$5.\$6)" , $text ) ; - $text = preg_replace ( "/(\d+)°(\d+)'(\d+)\" {$directions['S']}, (\d+)°(\d+)'(\d+)\" {$directions['W']}/" , "(GEO +\$1.\$2.\$3:-\$4.\$5.\$6)" , $text ) ; - $text = preg_replace ( "/(\d+)°(\d+)'(\d+)\" {$directions['S']}, (\d+)°(\d+)'(\d+)\" {$directions['E']}/" , "(GEO +\$1.\$2.\$3:+\$4.\$5.\$6)" , $text ) ; - - $a = split( 'GEO ', ' '.$text ); - if ( count ( $a ) < 2 ) { - wfProfileOut( $fname ); - return $text; - } - $text = substr( array_shift( $a ), 1); - $valid = '0123456789.+-:'; - - foreach ( $a as $x ) { - $geo = $blank = '' ; - while ( ' ' == $x{0} ) { - $blank .= ' '; - $x = substr( $x, 1 ); - } - while ( strstr( $valid, $x{0} ) != false ) { - $geo .= $x{0}; - $x = substr( $x, 1 ); - } - $num = str_replace( '+', '', $geo ); - $num = str_replace( ' ', '', $num ); - - if ( '' == $num || count ( explode ( ':' , $num , 3 ) ) < 2 ) { - $text .= "GEO $blank$x"; - } else { - $titleObj = Title::makeTitle( NS_SPECIAL, 'Geo' ); - $text .= 'GEO $geo"; - $text .= $x; - } - } - wfProfileOut( $fname ); - return $text; - } - /** * Return an HTML link for the "RFC 1234" text + * * @access private - * @param string $text text to be processed + * @param string $text Text to be processed + * @param string $keyword Magic keyword to use (default RFC) + * @param string $urlmsg Interface message to use (default rfcurl) + * @return string */ function magicRFC( $text, $keyword='RFC ', $urlmsg='rfcurl' ) { - global $wgLang; $valid = '0123456789'; $internal = false; @@ -2765,8 +2719,7 @@ class Parser $text .= $keyword.$id.$x; } else { /* build the external link*/ - $url = wfmsg( $urlmsg ); - $url = str_replace( '$1', $id, $url); + $url = wfMsg( $urlmsg, $id); $sk =& $this->mOptions->getSkin(); $la = $sk->getExternalLinkAttributes( $url, $keyword.$id ); $text .= "{$keyword}{$id}{$x}"; @@ -2802,7 +2755,7 @@ class Parser $stripState = false; $pairs = array( "\r\n" => "\n", - ); + ); $text = str_replace( array_keys( $pairs ), array_values( $pairs ), $text ); $text = $this->strip( $text, $stripState, false ); $text = $this->pstPass2( $text, $user ); @@ -2816,7 +2769,7 @@ class Parser * @access private */ function pstPass2( $text, &$user ) { - global $wgLang, $wgContLang, $wgLocaltimezone; + global $wgContLang, $wgLocaltimezone; # Variable replacement # Because mOutputType is OT_WIKI, this will only process {{subst:xxx}} type tags @@ -2831,11 +2784,16 @@ class Parser $oldtz = getenv( 'TZ' ); putenv( 'TZ='.$wgLocaltimezone ); } - /* Note: this is an ugly timezone hack for the European wikis */ - $d = $wgContLang->timeanddate( date( 'YmdHis' ), false ) . + + /* Note: This is the timestamp saved as hardcoded wikitext to + * the database, we use $wgContLang here in order to give + * everyone the same signiture and use the default one rather + * than the one selected in each users preferences. + */ + $d = $wgContLang->timeanddate( wfTimestampNow(), false, false) . ' (' . date( 'T' ) . ')'; if ( isset( $wgLocaltimezone ) ) { - putenv( 'TZ='.$oldtzs ); + putenv( 'TZ='.$oldtz ); } if( $user->getOption( 'fancysig' ) ) { @@ -2947,36 +2905,30 @@ class Parser * $options is a bit field, RLH_FOR_UPDATE to select for update */ function replaceLinkHolders( &$text, $options = 0 ) { - global $wgUser, $wgLinkCache, $wgUseOldExistenceCheck, $wgLinkHolders; - global $wgInterwikiLinkHolders; - global $outputReplace; - - if ( $wgUseOldExistenceCheck ) { - return array(); - } + global $wgUser, $wgLinkCache; + global $wgOutputReplace; $fname = 'Parser::replaceLinkHolders'; wfProfileIn( $fname ); $pdbks = array(); $colours = array(); + $sk = $this->mOptions->getSkin(); - #if ( !empty( $tmpLinks[0] ) ) { #TODO - if ( !empty( $wgLinkHolders['namespaces'] ) ) { + if ( !empty( $this->mLinkHolders['namespaces'] ) ) { wfProfileIn( $fname.'-check' ); $dbr =& wfGetDB( DB_SLAVE ); $page = $dbr->tableName( 'page' ); - $sk = $wgUser->getSkin(); $threshold = $wgUser->getOption('stubthreshold'); # Sort by namespace - asort( $wgLinkHolders['namespaces'] ); + asort( $this->mLinkHolders['namespaces'] ); # Generate query $query = false; - foreach ( $wgLinkHolders['namespaces'] as $key => $val ) { + foreach ( $this->mLinkHolders['namespaces'] as $key => $val ) { # Make title object - $title = $wgLinkHolders['titles'][$key]; + $title = $this->mLinkHolders['titles'][$key]; # Skip invalid entries. # Result will be ugly, but prevents crash. @@ -2994,16 +2946,11 @@ class Parser # Not in the link cache, add it to the query if ( !isset( $current ) ) { $current = $val; - $tables = $page; - $join = ''; $query = "SELECT page_id, page_namespace, page_title"; if ( $threshold > 0 ) { - $textTable = $dbr->tableName( 'text' ); - $query .= ', LENGTH(old_text) AS page_len, page_is_redirect'; - $tables .= ", $textTable"; - $join = 'page_latest=old_id AND'; + $query .= ', page_len, page_is_redirect'; } - $query .= " FROM $tables WHERE $join (page_namespace=$val AND page_title IN("; + $query .= " FROM $page WHERE (page_namespace=$val AND page_title IN("; } elseif ( $current != $val ) { $current = $val; $query .= ")) OR (page_namespace=$val AND page_title IN("; @@ -3011,7 +2958,7 @@ class Parser $query .= ', '; } - $query .= $dbr->addQuotes( $wgLinkHolders['dbkeys'][$key] ); + $query .= $dbr->addQuotes( $this->mLinkHolders['dbkeys'][$key] ); } } if ( $query ) { @@ -3029,11 +2976,11 @@ class Parser while ( $s = $dbr->fetchObject($res) ) { $title = Title::makeTitle( $s->page_namespace, $s->page_title ); $pdbk = $title->getPrefixedDBkey(); - $wgLinkCache->addGoodLink( $s->page_id, $pdbk ); + $wgLinkCache->addGoodLinkObj( $s->page_id, $title ); if ( $threshold > 0 ) { $size = $s->page_len; - if ( $s->page_is_redirect || $s->page_namespace != 0 || $length < $threshold ) { + if ( $s->page_is_redirect || $s->page_namespace != 0 || $size >= $threshold ) { $colours[$pdbk] = 1; } else { $colours[$pdbk] = 2; @@ -3047,25 +2994,25 @@ class Parser # Construct search and replace arrays wfProfileIn( $fname.'-construct' ); - $outputReplace = array(); - foreach ( $wgLinkHolders['namespaces'] as $key => $ns ) { + $wgOutputReplace = array(); + foreach ( $this->mLinkHolders['namespaces'] as $key => $ns ) { $pdbk = $pdbks[$key]; - $searchkey = ''; - $title = $wgLinkHolders['titles'][$key]; + $searchkey = ""; + $title = $this->mLinkHolders['titles'][$key]; if ( empty( $colours[$pdbk] ) ) { - $wgLinkCache->addBadLink( $pdbk ); + $wgLinkCache->addBadLinkObj( $title ); $colours[$pdbk] = 0; - $outputReplace[$searchkey] = $sk->makeBrokenLinkObj( $title, - $wgLinkHolders['texts'][$key], - $wgLinkHolders['queries'][$key] ); + $wgOutputReplace[$searchkey] = $sk->makeBrokenLinkObj( $title, + $this->mLinkHolders['texts'][$key], + $this->mLinkHolders['queries'][$key] ); } elseif ( $colours[$pdbk] == 1 ) { - $outputReplace[$searchkey] = $sk->makeKnownLinkObj( $title, - $wgLinkHolders['texts'][$key], - $wgLinkHolders['queries'][$key] ); + $wgOutputReplace[$searchkey] = $sk->makeKnownLinkObj( $title, + $this->mLinkHolders['texts'][$key], + $this->mLinkHolders['queries'][$key] ); } elseif ( $colours[$pdbk] == 2 ) { - $outputReplace[$searchkey] = $sk->makeStubLinkObj( $title, - $wgLinkHolders['texts'][$key], - $wgLinkHolders['queries'][$key] ); + $wgOutputReplace[$searchkey] = $sk->makeStubLinkObj( $title, + $this->mLinkHolders['texts'][$key], + $this->mLinkHolders['queries'][$key] ); } } wfProfileOut( $fname.'-construct' ); @@ -3075,24 +3022,75 @@ class Parser $text = preg_replace_callback( '/()/', - "outputReplaceMatches", + "wfOutputReplaceMatches", $text); + wfProfileOut( $fname.'-replace' ); } - if ( !empty( $wgInterwikiLinkHolders ) ) { + # Now process interwiki link holders + # This is quite a bit simpler than internal links + if ( !empty( $this->mInterwikiLinkHolders['texts'] ) ) { wfProfileIn( $fname.'-interwiki' ); - $outputReplace = $wgInterwikiLinkHolders; + # Make interwiki link HTML + $wgOutputReplace = array(); + foreach( $this->mInterwikiLinkHolders['texts'] as $key => $link ) { + $title = $this->mInterwikiLinkHolders['titles'][$key]; + $wgOutputReplace[$key] = $sk->makeLinkObj( $title, $link ); + } + $text = preg_replace_callback( '//', - "outputReplaceMatches", + "wfOutputReplaceMatches", $text ); wfProfileOut( $fname.'-interwiki' ); } - + wfProfileOut( $fname ); return $colours; } + + /** + * Replace link placeholders with plain text of links + * (not HTML-formatted). + * @param string $text + * @return string + */ + function replaceLinkHoldersText( $text ) { + global $wgUser, $wgLinkCache; + global $wgOutputReplace; + + $fname = 'Parser::replaceLinkHoldersText'; + wfProfileIn( $fname ); + + $text = preg_replace_callback( + '//', + array( &$this, 'replaceLinkHoldersTextCallback' ), + $text ); + + wfProfileOut( $fname ); + return $text; + } + + /** + * @param array $matches + * @return string + * @access private + */ + function replaceLinkHoldersTextCallback( $matches ) { + $type = $matches[1]; + $key = $matches[2]; + if( $type == 'LINK' ) { + if( isset( $this->mLinkHolders['texts'][$key] ) ) { + return $this->mLinkHolders['texts'][$key]; + } + } elseif( $type == 'IWLINK' ) { + if( isset( $this->mInterwikiLinkHolders['texts'][$key] ) ) { + return $this->mInterwikiLinkHolders['texts'][$key]; + } + } + return $matches[0]; + } /** * Renders an image gallery from a text with one line per image. @@ -3102,8 +3100,15 @@ class Parser * given as text will return the HTML of a gallery with two images, * labeled 'The number "1"' and * 'A tree'. + * + * @static */ function renderImageGallery( $text ) { + # Setup the parser + global $wgUser, $wgTitle; + $parserOptions = ParserOptions::newFromUser( $wgUser ); + $localParser = new Parser(); + global $wgLinkCache; $ig = new ImageGallery(); $ig->setShowBytes( false ); @@ -3129,15 +3134,92 @@ class Parser $label = ''; } - # FIXME: Use the full wiki parser and add its links - # to the page's links. - $html = $this->mOptions->mSkin->formatComment( $label ); + $html = $localParser->parse( $label , $wgTitle, $parserOptions ); + $html = $html->mText; - $ig->add( Image::newFromTitle( $nt ), $html ); + $ig->add( new Image( $nt ), $html ); $wgLinkCache->addImageLinkObj( $nt ); } return $ig->toHTML(); } + + /** + * Parse image options text and use it to make an image + */ + function makeImage( &$nt, $options ) { + global $wgContLang, $wgUseImageResize; + global $wgUser, $wgThumbLimits; + + $align = ''; + + # Check if the options text is of the form "options|alt text" + # Options are: + # * thumbnail make a thumbnail with enlarge-icon and caption, alignment depends on lang + # * left no resizing, just left align. label is used for alt= only + # * right same, but right aligned + # * none same, but not aligned + # * ___px scale to ___ pixels width, no aligning. e.g. use in taxobox + # * center center the image + # * framed Keep original image size, no magnify-button. + + $part = explode( '|', $options); + + $mwThumb =& MagicWord::get( MAG_IMG_THUMBNAIL ); + $mwLeft =& MagicWord::get( MAG_IMG_LEFT ); + $mwRight =& MagicWord::get( MAG_IMG_RIGHT ); + $mwNone =& MagicWord::get( MAG_IMG_NONE ); + $mwWidth =& MagicWord::get( MAG_IMG_WIDTH ); + $mwCenter =& MagicWord::get( MAG_IMG_CENTER ); + $mwFramed =& MagicWord::get( MAG_IMG_FRAMED ); + $caption = ''; + + $width = $height = $framed = $thumb = false; + $manual_thumb = "" ; + + foreach( $part as $key => $val ) { + $val_parts = explode ( "=" , $val , 2 ) ; + $left_part = array_shift ( $val_parts ) ; + if ( $wgUseImageResize && ! is_null( $mwThumb->matchVariableStartToEnd($val) ) ) { + $thumb=true; + } elseif ( $wgUseImageResize && count ( $val_parts ) == 1 && ! is_null( $mwThumb->matchVariableStartToEnd($left_part) ) ) { + # use manually specified thumbnail + $thumb=true; + $manual_thumb = array_shift ( $val_parts ) ; + } elseif ( ! is_null( $mwRight->matchVariableStartToEnd($val) ) ) { + # remember to set an alignment, don't render immediately + $align = 'right'; + } elseif ( ! is_null( $mwLeft->matchVariableStartToEnd($val) ) ) { + # remember to set an alignment, don't render immediately + $align = 'left'; + } elseif ( ! is_null( $mwCenter->matchVariableStartToEnd($val) ) ) { + # remember to set an alignment, don't render immediately + $align = 'center'; + } elseif ( ! is_null( $mwNone->matchVariableStartToEnd($val) ) ) { + # remember to set an alignment, don't render immediately + $align = 'none'; + } elseif ( $wgUseImageResize && ! is_null( $match = $mwWidth->matchVariableStartToEnd($val) ) ) { + wfDebug( "MAG_IMG_WIDTH match: $match\n" ); + # $match is the image width in pixels + if ( preg_match( '/^([0-9]*)x([0-9]*)$/', $match, $m ) ) { + $width = intval( $m[1] ); + $height = intval( $m[2] ); + } else { + $width = intval($match); + } + } elseif ( ! is_null( $mwFramed->matchVariableStartToEnd($val) ) ) { + $framed=true; + } else { + $caption = $val; + } + } + # Strip bad stuff out of the alt text + $alt = $this->replaceLinkHoldersText( $caption ); + $alt = Sanitizer::stripAllTags( $alt ); + + # Linker does the rest + $sk =& $this->mOptions->getSkin(); + return $sk->makeImageLinkObj( $nt, $caption, $alt, $align, $width, $height, $framed, $thumb, $manual_thumb ); + } } /** @@ -3147,11 +3229,12 @@ class Parser class ParserOutput { var $mText, $mLanguageLinks, $mCategoryLinks, $mContainsOldMagic; - var $mCacheTime; # Used in ParserCache + var $mCacheTime; # Timestamp on this article, or -1 for uncacheable. Used in ParserCache. var $mVersion; # Compatibility check + var $mTitleText; # title text of the chosen language variant function ParserOutput( $text = '', $languageLinks = array(), $categoryLinks = array(), - $containsOldMagic = false ) + $containsOldMagic = false, $titletext = '' ) { $this->mText = $text; $this->mLanguageLinks = $languageLinks; @@ -3159,18 +3242,22 @@ class ParserOutput $this->mContainsOldMagic = $containsOldMagic; $this->mCacheTime = ''; $this->mVersion = MW_PARSER_VERSION; + $this->mTitleText = $titletext; } function getText() { return $this->mText; } function getLanguageLinks() { return $this->mLanguageLinks; } function getCategoryLinks() { return array_keys( $this->mCategoryLinks ); } function getCacheTime() { return $this->mCacheTime; } + function getTitleText() { return $this->mTitleText; } function containsOldMagic() { return $this->mContainsOldMagic; } function setText( $text ) { return wfSetVar( $this->mText, $text ); } function setLanguageLinks( $ll ) { return wfSetVar( $this->mLanguageLinks, $ll ); } function setCategoryLinks( $cl ) { return wfSetVar( $this->mCategoryLinks, $cl ); } function setContainsOldMagic( $com ) { return wfSetVar( $this->mContainsOldMagic, $com ); } function setCacheTime( $t ) { return wfSetVar( $this->mCacheTime, $t ); } + function setTitleText( $t ) { return wfSetVar ($this->mTitleText, $t); } + function addCategoryLink( $c ) { $this->mCategoryLinks[$c] = 1; } function merge( $other ) { @@ -3190,7 +3277,8 @@ class ParserOutput */ function expired( $touched ) { global $wgCacheEpoch; - return $this->getCacheTime() <= $touched || + return $this->getCacheTime() == -1 || // parser says it's uncacheable + $this->getCacheTime() <= $touched || $this->getCacheTime() <= $wgCacheEpoch || !isset( $this->mVersion ) || version_compare( $this->mVersion, MW_PARSER_VERSION, "lt" ); @@ -3206,15 +3294,14 @@ class ParserOptions { # All variables are private var $mUseTeX; # Use texvc to expand tags - var $mUseDynamicDates; # Use $wgDateFormatter to format dates + var $mUseDynamicDates; # Use DateFormatter to format dates var $mInterwikiMagic; # Interlanguage links are removed and returned in an array var $mAllowExternalImages; # Allow external images inline var $mSkin; # Reference to the preferred skin var $mDateFormat; # Date format index var $mEditSection; # Create "edit section" links - var $mEditSectionOnRightClick; # Generate JavaScript to edit section on right click var $mNumberHeadings; # Automatically number headings - var $mShowToc; # Show table of contents + var $mAllowSpecialInclusion; # Allow inclusion of special pages function getUseTeX() { return $this->mUseTeX; } function getUseDynamicDates() { return $this->mUseDynamicDates; } @@ -3223,9 +3310,9 @@ class ParserOptions function getSkin() { return $this->mSkin; } function getDateFormat() { return $this->mDateFormat; } function getEditSection() { return $this->mEditSection; } - function getEditSectionOnRightClick() { return $this->mEditSectionOnRightClick; } function getNumberHeadings() { return $this->mNumberHeadings; } - function getShowToc() { return $this->mShowToc; } + function getAllowSpecialInclusion() { return $this->mAllowSpecialInclusion; } + function setUseTeX( $x ) { return wfSetVar( $this->mUseTeX, $x ); } function setUseDynamicDates( $x ) { return wfSetVar( $this->mUseDynamicDates, $x ); } @@ -3233,22 +3320,30 @@ class ParserOptions function setAllowExternalImages( $x ) { return wfSetVar( $this->mAllowExternalImages, $x ); } function setDateFormat( $x ) { return wfSetVar( $this->mDateFormat, $x ); } function setEditSection( $x ) { return wfSetVar( $this->mEditSection, $x ); } - function setEditSectionOnRightClick( $x ) { return wfSetVar( $this->mEditSectionOnRightClick, $x ); } function setNumberHeadings( $x ) { return wfSetVar( $this->mNumberHeadings, $x ); } - function setShowToc( $x ) { return wfSetVar( $this->mShowToc, $x ); } + function setAllowSpecialInclusion( $x ) { return wfSetVar( $this->mAllowSpecialInclusion, $x ); } function setSkin( &$x ) { $this->mSkin =& $x; } - # Get parser options - /* static */ function newFromUser( &$user ) { + function ParserOptions() { + global $wgUser; + $this->initialiseFromUser( $wgUser ); + } + + /** + * Get parser options + * @static + */ + function newFromUser( &$user ) { $popts = new ParserOptions; $popts->initialiseFromUser( $user ); return $popts; } - # Get user options + /** Get user options */ function initialiseFromUser( &$userInput ) { - global $wgUseTeX, $wgUseDynamicDates, $wgInterwikiMagic, $wgAllowExternalImages; + global $wgUseTeX, $wgUseDynamicDates, $wgInterwikiMagic, $wgAllowExternalImages, + $wgAllowSpecialInclusion; $fname = 'ParserOptions::initialiseFromUser'; wfProfileIn( $fname ); if ( !$userInput ) { @@ -3267,22 +3362,19 @@ class ParserOptions wfProfileOut( $fname.'-skin' ); $this->mDateFormat = $user->getOption( 'date' ); $this->mEditSection = $user->getOption( 'editsection' ); - $this->mEditSectionOnRightClick = $user->getOption( 'editsectiononrightclick' ); $this->mNumberHeadings = $user->getOption( 'numberheadings' ); - $this->mShowToc = $user->getOption( 'showtoc' ); + $this->mAllowSpecialInclusion = $wgAllowSpecialInclusion; wfProfileOut( $fname ); } - - } /** * Callback function used by Parser::replaceLinkHolders() * to substitute link placeholders. */ -function &outputReplaceMatches( $matches ) { - global $outputReplace; - return $outputReplace[$matches[1]]; +function &wfOutputReplaceMatches( $matches ) { + global $wgOutputReplace; + return $wgOutputReplace[$matches[1]]; } /** @@ -3319,6 +3411,13 @@ function wfLoadSiteStats() { } } +/** + * Escape html tags + * Basicly replacing " > and < with HTML entities ( ", >, <) + * + * @param string $in Text that might contain HTML tags + * @return string Escaped string + */ function wfEscapeHTMLTagsOnly( $in ) { return str_replace( array( '"', '>', '<' ),