13 define(
'MW_PARSER_VERSION',
'1.6.1');
15 define(
'RLH_FOR_UPDATE', 1);
17 # Allowed values for $mOutputType 21 define(
'OT_PREPROCESS', 4);
23 # Flags for setFunctionHook 24 define(
'SFH_NO_HASH', 1);
26 # string parameter for extractTags which will cause it 27 # to strip HTML comments in addition to regular 28 # <XML>-style tags. This should not be anything we 29 # may want to use in wikisyntax 30 define(
'STRIP_COMMENTS',
'HTMLCommentStrip');
32 # Constants needed for external link processing 33 define(
'HTTP_PROTOCOLS',
'http:\/\/|https:\/\/');
34 # Everything except bracket, space, or control characters 35 define(
'EXT_LINK_URL_CLASS',
'[^][<>"\\x00-\\x20\\x7F]');
36 # Including space, but excluding newlines 37 define(
'EXT_LINK_TEXT_CLASS',
'[^\]\\x0a\\x0d]');
38 define(
'EXT_IMAGE_FNAME_CLASS',
'[A-Za-z0-9_.,~%\\-+&;#*?!=()@\\x80-\\xFF]');
39 define(
'EXT_IMAGE_EXTENSIONS',
'gif|png|jpg|jpeg');
50 define(
'MW_COLON_STATE_TEXT', 0);
51 define(
'MW_COLON_STATE_TAG', 1);
52 define(
'MW_COLON_STATE_TAGSTART', 2);
53 define(
'MW_COLON_STATE_CLOSETAG', 3);
54 define(
'MW_COLON_STATE_TAGSLASH', 4);
55 define(
'MW_COLON_STATE_COMMENT', 5);
56 define(
'MW_COLON_STATE_COMMENTDASH', 6);
57 define(
'MW_COLON_STATE_COMMENTDASHDASH', 7);
104 # Cleared with clearState(): 125 # These are variables reset at least once per parse regardless of $clearState 149 $this->mTagHooks = array();
150 $this->mFunctionHooks = array();
151 $this->mFunctionSynonyms = array( 0 => array(), 1 => array() );
152 $this->mFirstCall =
true;
160 if (!$this->mFirstCall) {
164 wfProfileIn(__METHOD__);
165 global $wgAllowDisplayTitle, $wgAllowSlowParserFunctions;
167 $this->
setHook(
'pre', array( $this,
'renderPreTag' ));
193 $this->
setFunctionHook(
'special', array(
'CoreParserFunctions',
'special' ));
196 if ($wgAllowDisplayTitle) {
199 if ($wgAllowSlowParserFunctions) {
204 $this->mFirstCall =
false;
205 wfProfileOut(__METHOD__);
215 wfProfileIn(__METHOD__);
216 if ($this->mFirstCall) {
219 $this->mOutput =
new ParserOutput;
220 $this->mAutonumber = 0;
221 $this->mLastSection =
'';
222 $this->mDTopen =
false;
223 $this->mIncludeCount = array();
225 $this->mArgStack = array();
226 $this->mInPre =
false;
227 $this->mInterwikiLinkHolders = array(
231 $this->mLinkHolders = array(
232 'namespaces' => array(),
234 'queries' => array(),
238 $this->mRevisionTimestamp = $this->mRevisionId = null;
249 # Clear these on every parse, bug 4549 250 $this->mTemplates = array();
251 $this->mTemplatePath = array();
253 $this->mShowToc =
true;
254 $this->mForceTocPosition =
false;
255 $this->mIncludeSizes = array(
260 $this->mDefaultSort =
false;
262 wfRunHooks(
'ParserClearState', array( &$this ));
263 wfProfileOut(__METHOD__);
268 $this->mOutputType =
$ot;
307 global $wgUseTidy, $wgAlwaysUseTidy, $wgContLang;
308 $fname =
'Parser::parse-' . wfGetCaller();
309 wfProfileIn(__METHOD__);
320 if ($revid !== null) {
321 $this->mRevisionId = $revid;
322 $this->mRevisionTimestamp = null;
325 wfRunHooks(
'ParserBeforeStrip', array( &$this, &
$text, &$this->mStripState ));
327 wfRunHooks(
'ParserAfterStrip', array( &$this, &
$text, &$this->mStripState ));
329 $text = $this->mStripState->unstripGeneral(
$text);
331 # Clean up special characters, only run once, next-to-last before doBlockLevels 333 # french spaces, last one Guillemet-
left 334 # only
if there
is something before
the space
335 '/(.) (?=\\?|:|;|!|\\302\\273)/' =>
'\\1 \\2',
336 # french spaces, Guillemet-right
337 '/(\\302\\253) /' =>
'\\1 ',
339 $text = preg_replace(array_keys($fixtags), array_values($fixtags),
$text);
346 # the position of the parserConvert() call should not be changed. it 347 # assumes that the links are all replaced and the only thing left 348 # is the <nowiki> mark. 349 # Side-effects: this calls $this->mOutput->setTitleText() 350 $text = $wgContLang->parserConvert(
$text, $this);
352 $text = $this->mStripState->unstripNoWiki(
$text);
354 wfRunHooks(
'ParserBeforeTidy', array( &$this, &
$text ));
358 if (($wgUseTidy
and $this->mOptions->mTidy)
or $wgAlwaysUseTidy) {
361 # attempt to sanitize at least some nesting problems 362 # (bug #2702 and quite a few others) 365 # <i>Something</i><a href="http://www.cool.com"..><i>cool></i></
a>
366 '/(<([bi])>)(<([bi])>)?([^<]*)(<\/?a[^<]*>)([^<]*)(<\/\\4>)?(<\/\\2>)/' =>
367 '\\1\\3\\5\\8\\9\\6\\1\\3\\7\\8\\9',
368 # fix
up an anchor inside another anchor, only
369 # at least
for a single single nested link (bug 3695)
370 '/(<a[^>]+>)([^<]*)(<a[^>]+>[^<]*)<\/a>(.*)<\/a>/' =>
371 '\\1\\2</a>\\3</a>\\1\\4</a>',
372 # fix div inside
inline elements-
doBlockLevels won
't wrap a line which 373 # contains a div, so fix it up here; replace 374 # div with escaped text 375 '/(<([aib]) [^>]+>)([^<]*)(<div([^>]*)>)(.*)(<\/div>)([^<]*)(<\/\\2>)/
' => 376 '\\1\\3<div\\5>\\6</div>\\8\\9
', 377 # remove empty italic or bold tag pairs, some 378 # introduced by rules above 379 '/<([bi])><\/\\1>/
' => '', 382 $text = preg_replace( 383 array_keys($tidyregs), 384 array_values($tidyregs), 389 wfRunHooks('ParserAfterTidy
', array( &$this, &$text )); 391 # Information on include size limits, for the benefit of users who try to skirt them 392 if (max($this->mIncludeSizes) > 1000) { 393 $max = $this->mOptions->getMaxIncludeSize(); 395 "Pre-expand include size: {$this->mIncludeSizes['pre-
expand']} bytes\n" . 396 "Post-expand include size: {$this->mIncludeSizes['post-
expand']} bytes\n" . 397 "Template argument size: {$this->mIncludeSizes['arg
']} bytes\n" . 398 "Maximum: $max bytes\n" . 401 $this->mOutput->setText($text); 402 $this->mRevisionId = $oldRevisionId; 403 $this->mRevisionTimestamp = $oldRevisionTimestamp; 404 wfProfileOut($fname); 405 wfProfileOut(__METHOD__); 407 return $this->mOutput; 414 public function recursiveTagParse($text) 416 wfProfileIn(__METHOD__); 417 wfRunHooks('ParserBeforeStrip
', array( &$this, &$text, &$this->mStripState )); 418 $text = $this->strip($text, $this->mStripState); 419 wfRunHooks('ParserAfterStrip
', array( &$this, &$text, &$this->mStripState )); 420 $text = $this->internalParse($text); 421 wfProfileOut(__METHOD__); 429 public function preprocess($text, $title, $options) 431 wfProfileIn(__METHOD__); 433 $this->setOutputType(OT_PREPROCESS); 434 $this->mOptions = $options; 435 $this->mTitle = $title; 436 wfRunHooks('ParserBeforeStrip
', array( &$this, &$text, &$this->mStripState )); 437 $text = $this->strip($text, $this->mStripState); 438 wfRunHooks('ParserAfterStrip
', array( &$this, &$text, &$this->mStripState )); 439 if ($this->mOptions->getRemoveComments()) { 440 $text = Sanitizer::removeHTMLcomments($text); 442 $text = $this->replaceVariables($text); 443 $text = $this->mStripState->unstripBoth($text); 444 wfProfileOut(__METHOD__); 454 public function getRandomString() 456 return dechex(mt_rand(0, 0x7fffffff)) . dechex(mt_rand(0, 0x7fffffff)); 459 public function &getTitle() 461 return $this->mTitle; 463 public function getOptions() 465 return $this->mOptions; 468 public function getFunctionLang() 470 global $wgLang, $wgContLang; 471 return $this->mOptions->getInterfaceMessage() ? $wgLang : $wgContLang; 492 public function extractTagsAndParams($elements, $text, &$matches, $uniq_prefix = '') 498 $taglist = implode('|
', $elements); 499 $start = "/<($taglist)(\\s+[^>]*?|\\s*?)(\/?>)|<(!--)/i"; 501 while ('' != $text) { 502 $p = preg_split($start, $text, 2, PREG_SPLIT_DELIM_CAPTURE); 521 $marker = "$uniq_prefix-$element-" . sprintf('%08X
', $n++) . '-QINU
'; 522 $stripped .= $marker; 524 if ($close === '/>
') { 525 // Empty element tag, <tag /> 530 if ($element == '!--
') { 533 $end = "/(<\\/$element\\s*>)/i"; 535 $q = preg_split($end, $inside, 2, PREG_SPLIT_DELIM_CAPTURE); 538 # No end tag -- let it run out to the end of the text. 547 $matches[$marker] = array( $element, 549 Sanitizer::decodeTagAttributes($attributes), 550 "<$element$attributes$close$content$tail" ); 572 public function strip($text, $state, $stripcomments = false, $dontstrip = array()) 575 wfProfileIn(__METHOD__); 576 $render = ($this->mOutputType == OT_HTML); 578 $uniq_prefix = $this->mUniqPrefix; 579 $commentState = new ReplacementArray; 580 $nowikiItems = array(); 581 $generalItems = array(); 583 $elements = array_merge( 584 array( 'nowiki
', 'gallery
' ), 585 array_keys($this->mTagHooks) 589 $elements[] = 'html'; 591 if ($this->mOptions->getUseTeX()) { 592 $elements[] = 'math
'; 595 # Removing $dontstrip tags from $elements list (currently only 'gallery
', fixing bug 2700) 596 foreach ($elements as $k => $v) { 597 if (!in_array($v, $dontstrip)) { 600 unset($elements[$k]); 604 $text = Parser::extractTagsAndParams($elements, $text, $matches, $uniq_prefix); 606 foreach ($matches as $marker => $data) { 607 list($element, $content, $params, $tag) = $data; 609 $tagName = strtolower($element); 610 wfProfileIn(__METHOD__ . "-render-$tagName"); 614 if (substr($tag, -3) == '-->
') { 617 // Unclosed comment in input. 618 // Close it so later stripping can remove it 627 // Shouldn't happen otherwise. :)
630 $output = Xml::escapeTagsOnly($content);
633 $output = $wgContLang->armourMath(MathRenderer::renderMath($content));
639 if (isset($this->mTagHooks[$tagName])) {
640 $output = call_user_func_array(
641 $this->mTagHooks[$tagName],
642 array( $content,
$params, $this )
645 throw new MWException(
"Invalid call hook $element");
648 wfProfileOut(__METHOD__ .
"-render-$tagName");
657 if (!$stripcomments && $element ==
'!--') {
658 $commentState->setPair($marker,
$output);
659 } elseif ($element ==
'html' || $element ==
'nowiki') {
660 $nowikiItems[$marker] =
$output;
662 $generalItems[$marker] =
$output;
665 # Add the new items to the state 666 # We do this after the loop instead of during it to avoid slowing 667 # down the recursive unstrip 668 $state->nowiki->mergeArray($nowikiItems);
669 $state->general->mergeArray($generalItems);
671 # Unstrip comments unless explicitly told otherwise. 672 # (The comments are always stripped prior to this point, so as to 673 # not invoke any extension tags / parser hooks contained within 675 if (!$stripcomments) {
680 wfProfileOut(__METHOD__);
712 return $this->mStripState->unstripBoth(
$text);
745 global $wgTidyInternal;
746 $wrappedtext =
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"' .
747 ' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html>' .
748 '<head><title>test</title></head><body>' .
$text .
'</body></html>';
749 if ($wgTidyInternal) {
754 if (is_null($correctedtext)) {
755 wfDebug(
"Tidy error detected!\n");
756 return $text .
"\n<!-- Tidy found serious XHTML errors -->\n";
758 return $correctedtext;
769 global $wgTidyConf, $wgTidyBin, $wgTidyOpts;
770 $fname =
'Parser::externalTidy';
776 $descriptorspec = array(
777 0 => array(
'pipe',
'r'),
778 1 => array(
'pipe',
'w'),
779 2 => array(
'file',
'/dev/null',
'a')
782 $process = proc_open(
"$wgTidyBin -config $wgTidyConf $wgTidyOpts$opts", $descriptorspec, $pipes);
783 if (is_resource($process)) {
789 fwrite($pipes[0],
$text);
791 while (!feof($pipes[1])) {
792 $cleansource .= fgets($pipes[1], 1024);
795 proc_close($process);
798 wfProfileOut($fname);
800 if ($cleansource ==
'' &&
$text !=
'') {
822 $fname =
'Parser::internalTidy';
825 tidy_load_config($wgTidyConf);
826 tidy_set_encoding(
'utf8');
827 tidy_parse_string(
$text);
829 if (tidy_get_status() == 2) {
834 $cleansource = tidy_get_output();
836 wfProfileOut($fname);
847 $fname =
'Parser::doTableStuff';
850 $lines = explode(
"\n",
$text);
851 $td_history = array();
852 $last_tag_history = array();
853 $tr_history = array();
854 $tr_attributes = array();
855 $has_opened_tr = array();
857 foreach ($lines as
$key => $line) {
863 $first_character = $line[0];
866 if (preg_match(
'/^(:*)\{\|(.*)$/', $line, $matches)) {
868 $indent_level = strlen($matches[1]);
870 $attributes = $this->mStripState->unstripBoth($matches[2]);
873 $lines[
$key] = str_repeat(
'<dl><dd>', $indent_level) .
"<table{$attributes}>";
874 array_push($td_history,
false);
875 array_push($last_tag_history,
'');
876 array_push($tr_history,
false);
877 array_push($tr_attributes,
'');
878 array_push($has_opened_tr,
false);
879 } elseif (count($td_history) == 0) {
882 } elseif (substr($line, 0, 2) ==
'|}') {
884 $line =
'</table>' . substr($line, 2);
885 $last_tag = array_pop($last_tag_history);
887 if (!array_pop($has_opened_tr)) {
888 $line =
"<tr><td></td></tr>{$line}";
891 if (array_pop($tr_history)) {
892 $line =
"</tr>{$line}";
895 if (array_pop($td_history)) {
896 $line =
"</{$last_tag}>{$line}";
898 array_pop($tr_attributes);
899 $lines[
$key] = $line . str_repeat(
'</dd></dl>', $indent_level);
900 } elseif (substr($line, 0, 2) ==
'|-') {
902 $line = preg_replace(
'#^\|-+#',
'', $line);
905 $attributes = $this->mStripState->unstripBoth($line);
907 array_pop($tr_attributes);
911 $last_tag = array_pop($last_tag_history);
912 array_pop($has_opened_tr);
913 array_push($has_opened_tr,
true);
915 if (array_pop($tr_history)) {
919 if (array_pop($td_history)) {
920 $line =
"</{$last_tag}>{$line}";
923 $lines[
$key] = $line;
924 array_push($tr_history,
false);
925 array_push($td_history,
false);
926 array_push($last_tag_history,
'');
927 } elseif ($first_character ==
'|' || $first_character ==
'!' || substr($line, 0, 2) ==
'|+') {
929 if (substr($line, 0, 2) ==
'|+') {
930 $first_character =
'+';
931 $line = substr($line, 1);
934 $line = substr($line, 1);
936 if ($first_character ==
'!') {
937 $line = str_replace(
'!!',
'||', $line);
944 $cells = StringUtils::explodeMarkup(
'||', $line);
949 foreach ($cells as $cell) {
951 if ($first_character !=
'+') {
952 $tr_after = array_pop($tr_attributes);
953 if (!array_pop($tr_history)) {
954 $previous =
"<tr{$tr_after}>\n";
956 array_push($tr_history,
true);
957 array_push($tr_attributes,
'');
958 array_pop($has_opened_tr);
959 array_push($has_opened_tr,
true);
962 $last_tag = array_pop($last_tag_history);
964 if (array_pop($td_history)) {
965 $previous =
"</{$last_tag}>{$previous}";
968 if ($first_character ==
'|') {
970 } elseif ($first_character ==
'!') {
972 } elseif ($first_character ==
'+') {
973 $last_tag =
'caption';
978 array_push($last_tag_history, $last_tag);
981 $cell_data = explode(
'|', $cell, 2);
985 if (strpos($cell_data[0],
'[[') !==
false) {
986 $cell =
"{$previous}<{$last_tag}>{$cell}";
987 } elseif (count($cell_data) == 1) {
988 $cell =
"{$previous}<{$last_tag}>{$cell_data[0]}";
990 $attributes = $this->mStripState->unstripBoth($cell_data[0]);
992 $cell =
"{$previous}<{$last_tag}{$attributes}>{$cell_data[1]}";
995 $lines[
$key] .= $cell;
996 array_push($td_history,
true);
1002 while (count($td_history) > 0) {
1003 if (array_pop($td_history)) {
1004 $lines[] =
'</td>' ;
1006 if (array_pop($tr_history)) {
1007 $lines[] =
'</tr>' ;
1009 if (!array_pop($has_opened_tr)) {
1010 $lines[] =
"<tr><td></td></tr>" ;
1013 $lines[] =
'</table>' ;
1016 $output = implode(
"\n", $lines) ;
1019 if (
$output ==
"<table>\n<tr><td></td></tr>\n</table>") {
1023 wfProfileOut($fname);
1038 $fname =
'Parser::internalParse';
1039 wfProfileIn($fname);
1041 # Hook to suspend the parser in this state 1042 if (!wfRunHooks(
'ParserBeforeInternalParse', array( &$this, &
$text, &$this->mStripState ))) {
1043 wfProfileOut($fname);
1047 # Remove <noinclude> tags and <includeonly> sections 1048 $text = strtr(
$text, array(
'<onlyinclude>' =>
'' ,
'</onlyinclude>' =>
'' ));
1049 $text = strtr(
$text, array(
'<noinclude>' =>
'',
'</noinclude>' =>
''));
1050 $text = StringUtils::delimiterReplace(
'<includeonly>',
'</includeonly>',
'',
$text);
1055 wfRunHooks(
'InternalParseBeforeLinks', array( &$this, &
$text, &$this->mStripState ));
1063 $text = preg_replace(
'/(^|\n)-----*/',
'\\1<hr />',
$text);
1068 if ($this->mOptions->getUseDynamicDates()) {
1069 $df = &DateFormatter::getInstance();
1070 $text = $df->reformat($this->mOptions->getDateFormat(),
$text);
1076 # replaceInternalLinks may sometimes leave behind 1077 # absolute URLs, which have to be masked to hide them from replaceExternalLinks 1078 $text = str_replace($this->mUniqPrefix .
"NOPARSE",
"",
$text);
1083 wfProfileOut($fname);
1095 wfProfileIn(__METHOD__);
1096 $text = preg_replace_callback(
1098 <a.*?</a> | # Skip link text 1099 <.*?> | # Skip stuff inside HTML elements 1100 (?:RFC|PMID)\s+([0-9]+) | # RFC or PMID, capture number as m[1] 1101 ISBN\s+(\b # ISBN, capture number as m[2] 1102 (?: 97[89] [\ \-]? )? # optional 13-digit ISBN prefix 1103 (?: [0-9] [\ \-]? ){9} # 9 digits with opt. delimiters 1104 [0-9Xx] # check digit 1107 array( &$this,
'magicLinkCallback' ),
1110 wfProfileOut(__METHOD__);
1116 if (substr(
$m[0], 0, 1) ==
'<') {
1119 } elseif (substr(
$m[0], 0, 4) ==
'ISBN') {
1121 $num = strtr($isbn, array(
1126 $titleObj = SpecialPage::getTitleFor(
'Booksources');
1127 $text =
'<a href="' .
1128 $titleObj->escapeLocalUrl(
"isbn=$num") .
1129 "\" class=\"internal\">ISBN $isbn</a>";
1131 if (substr(
$m[0], 0, 3) ==
'RFC') {
1135 } elseif (substr(
$m[0], 0, 4) ==
'PMID') {
1137 $urlmsg =
'pubmedurl';
1140 throw new MWException(__METHOD__ .
': unrecognised match type "' .
1141 substr(
$m[0], 0, 20) .
'"');
1145 $sk = $this->mOptions->getSkin();
1146 $la = $sk->getExternalLinkAttributes(
$url, $keyword .
$id);
1147 $text =
"<a href=\"{$url}\"{$la}>{$keyword} {$id}</a>";
1159 $fname =
'Parser::doHeadings';
1160 wfProfileIn($fname);
1161 for (
$i = 6;
$i >= 1; --
$i) {
1162 $h = str_repeat(
'=',
$i);
1163 $text = preg_replace(
1164 "/^{$h}(.+){$h}\\s*$/m",
1165 "<h{$i}>\\1</h{$i}>\\2",
1169 wfProfileOut($fname);
1180 $fname =
'Parser::doAllQuotes';
1181 wfProfileIn($fname);
1183 $lines = explode(
"\n",
$text);
1184 foreach ($lines as $line) {
1185 $outtext .= $this->
doQuotes($line) .
"\n";
1187 $outtext = substr($outtext, 0, -1);
1188 wfProfileOut($fname);
1198 $arr = preg_split(
"/(''+)/",
$text, -1, PREG_SPLIT_DELIM_CAPTURE);
1199 if (count($arr) == 1) {
1202 # First, do some preliminary work. This may shift some apostrophes from 1203 # being mark-up to being text. It also counts the number of occurrences 1204 # of bold and italics mark-ups. 1208 foreach ($arr as
$r) {
1209 if ((
$i % 2) == 1) {
1210 # If there are ever four apostrophes, assume the first is supposed to 1211 # be text, and the remaining three constitute mark-up for bold text. 1212 if (strlen($arr[
$i]) == 4) {
1213 $arr[$i - 1] .=
"'";
1216 # If there are more than 5 apostrophes in a row, assume they're all 1217 # text except for the last 5. 1218 elseif (strlen($arr[$i]) > 5) {
1219 $arr[$i - 1] .= str_repeat(
"'", strlen($arr[$i]) - 5);
1222 # Count the number of occurrences of bold and italics mark-ups. 1223 # We are not counting sequences of five apostrophes. 1224 if (strlen($arr[$i]) == 2) {
1226 } elseif (strlen($arr[$i]) == 3) {
1228 } elseif (strlen($arr[$i]) == 5) {
1236 # If there is an odd number of both bold and italics, it is likely 1237 # that one of the bold ones was meant to be an apostrophe followed 1238 # by italics. Which one we cannot know for certain, but it is more 1239 # likely to be one that has a single-letter word before it. 1240 if (($numbold % 2 == 1) && ($numitalics % 2 == 1)) {
1242 $firstsingleletterword = -1;
1243 $firstmultiletterword = -1;
1245 foreach ($arr as $r) {
1246 if ((
$i % 2 == 1) and (strlen($r) == 3)) {
1247 $x1 = substr($arr[
$i - 1], -1);
1248 $x2 = substr($arr[
$i - 1], -2, 1);
1250 if ($firstspace == -1) {
1253 } elseif ($x2 ==
' ') {
1254 if ($firstsingleletterword == -1) {
1255 $firstsingleletterword =
$i;
1258 if ($firstmultiletterword == -1) {
1259 $firstmultiletterword =
$i;
1266 # If there is a single-letter word, use it! 1267 if ($firstsingleletterword > -1) {
1268 $arr [ $firstsingleletterword ] =
"''";
1269 $arr [ $firstsingleletterword - 1 ] .=
"'";
1271 # If not, but there's a multi-letter word, use that one. 1272 elseif ($firstmultiletterword > -1) {
1273 $arr [ $firstmultiletterword ] =
"''";
1274 $arr [ $firstmultiletterword - 1 ] .=
"'";
1276 # ... otherwise use the first one that has neither. 1277 # (notice that it is possible for all three to be -1 if, for example, 1278 # there is only one pentuple-apostrophe in the line) 1279 elseif ($firstspace > -1) {
1280 $arr [ $firstspace ] =
"''";
1281 $arr [ $firstspace - 1 ] .=
"'";
1285 # Now let's actually convert our apostrophic mush to HTML! 1290 foreach ($arr as $r) {
1291 if ((
$i % 2) == 0) {
1298 if (strlen($r) == 2) {
1302 } elseif (
$state ==
'bi') {
1305 } elseif (
$state ==
'ib') {
1308 } elseif (
$state ==
'both') {
1309 $output .=
'<b><i>' . $buffer .
'</i>';
1315 } elseif (strlen($r) == 3) {
1319 } elseif (
$state ==
'bi') {
1322 } elseif (
$state ==
'ib') {
1325 } elseif (
$state ==
'both') {
1326 $output .=
'<i><b>' . $buffer .
'</b>';
1332 } elseif (strlen($r) == 5) {
1336 } elseif (
$state ==
'i') {
1339 } elseif (
$state ==
'bi') {
1342 } elseif (
$state ==
'ib') {
1345 } elseif (
$state ==
'both') {
1346 $output .=
'<i><b>' . $buffer .
'</b></i>';
1348 }
else { # (
$state ==
'')
1356 # Now close all remaining tags. Notice that the order is important. 1366 # There might be lonely ''''', so make sure we have a buffer 1367 if (
$state ==
'both' && $buffer) {
1368 $output .=
'<b><i>' . $buffer .
'</i></b>';
1385 $fname =
'Parser::replaceExternalLinks';
1386 wfProfileIn($fname);
1388 $sk = $this->mOptions->getSkin();
1395 while (
$i < count($bits)) {
1399 $trail = $bits[
$i++];
1401 # The characters '<' and '>' (which were escaped by 1402 # removeHTMLtags()) should not be included in 1403 # URLs, per RFC 2396. 1405 if (preg_match(
'/&(lt|gt);/',
$url, $m2, PREG_OFFSET_CAPTURE)) {
1410 # If the link text is an image URL, replace it with an <img> tag 1411 # This happened by accident in the original parser, but some people used it extensively 1413 if (
$img !==
false) {
1419 # Set linktype for CSS - if URL==text, link is essentially free 1420 $linktype = (
$text ==
$url) ?
'free' :
'text';
1422 # No link text, e.g. [http://domain.tld/some.link] 1424 # Autonumber if allowed. See bug #5918 1426 $text =
'[' . ++$this->mAutonumber .
']';
1427 $linktype =
'autonumber';
1429 # Otherwise just use the URL 1434 # Have link text, e.g. [http://domain.tld/some.link text]s 1436 list($dtrail, $trail) = Linker::splitTrail($trail);
1439 $text = $wgContLang->markNoConversion(
$text);
1443 # Process the trail (i.e. everything after this link up until start of the next link), 1444 # replacing any non-bracketed links 1447 # Use the encoded URL 1448 # This means that users can paste URLs directly into the text 1449 # Funny characters like ö aren't valid in URLs anyway 1450 # This was changed in August 2004 1451 $s .= $sk->makeExternalLink(
$url,
$text,
false, $linktype, $this->mTitle->getNamespace()) . $dtrail . $trail;
1453 # Register link in the output object. 1454 # Replace unnecessary URL escape codes with the referenced character 1455 # This prevents spammers from hiding links from the filters 1457 $this->mOutput->addExternalLink($pasteurized);
1460 wfProfileOut($fname);
1472 $fname =
'Parser::replaceFreeExternalLinks';
1473 wfProfileIn($fname);
1475 $bits = preg_split(
'/(\b(?:' .
wfUrlProtocols() .
'))/S',
$text, -1, PREG_SPLIT_DELIM_CAPTURE);
1476 $s = array_shift($bits);
1479 $sk = $this->mOptions->getSkin();
1481 while (
$i < count($bits)) {
1483 $remainder = $bits[
$i++];
1487 # Found some characters after the protocol that look promising 1491 # special case: handle urls as url args: 1492 # http://www.example.com/foo?=http://www.example.com/bar 1493 if (strlen($trail) == 0 &&
1498 $url .= $bits[
$i] .
$m[1]; # protocol, url as arg to previous link
1503 # The characters '<' and '>' (which were escaped by 1504 # removeHTMLtags()) should not be included in 1505 # URLs, per RFC 2396. 1507 if (preg_match(
'/&(lt|gt);/',
$url, $m2, PREG_OFFSET_CAPTURE)) {
1508 $trail = substr(
$url, $m2[0][1]) . $trail;
1512 # Move trailing punctuation to $trail 1514 # If there is no left bracket, then consider right brackets fair game too 1515 if (strpos(
$url,
'(') ===
false) {
1519 $numSepChars = strspn(strrev(
$url), $sep);
1521 $trail = substr(
$url, -$numSepChars) . $trail;
1522 $url = substr(
$url, 0, -$numSepChars);
1526 # Is this an external image? 1528 if (
$text ===
false) {
1529 # Not an image, make a link 1530 $text = $sk->makeExternalLink(
$url, $wgContLang->markNoConversion(
$url),
true,
'free', $this->mTitle->getNamespace());
1531 # Register it in the output object... 1532 # Replace unnecessary URL escape codes with their equivalent characters 1535 $this->mOutput->addExternalLink($pasteurized);
1542 wfProfileOut($fname);
1558 return preg_replace_callback(
1559 '/%[0-9A-Fa-f]{2}/',
1560 array(
'Parser',
'replaceUnusualEscapesCallback' ),
1573 $char = urldecode($matches[0]);
1577 if ($ord > 32 && $ord < 127 && strpos(
'<>"#{}|\^~[]`;/?', $char) ===
false) {
1593 $sk = $this->mOptions->getSkin();
1594 $imagesfrom = $this->mOptions->getAllowExternalImagesFrom();
1595 $imagesexception = !empty($imagesfrom);
1597 if ($this->mOptions->getAllowExternalImages()
1598 || ($imagesexception && strpos(
$url, $imagesfrom) === 0)) {
1601 $text = $sk->makeExternalImage(htmlspecialchars(
$url));
1615 static $fname =
'Parser::replaceInternalLinks' ;
1617 wfProfileIn($fname);
1619 wfProfileIn($fname .
'-setup');
1621 # the % is needed to support urlencoded titles as well 1626 $sk = $this->mOptions->getSkin();
1628 #split the entire text string on occurences of [[ 1629 $a = explode(
'[[',
' ' .
$s);
1630 #get the first element (all text up to first [[), and remove the space we added 1631 $s = array_shift($a);
1634 # Match a link having the form [[namespace:link|alternate]]trail 1637 $e1 =
"/^([{$tc}]+)(?:\\|(.+?))?]](.*)\$/sD";
1639 # Match cases where there is no "]]", which might still be images 1640 static $e1_img =
false;
1642 $e1_img =
"/^([{$tc}]+)\\|(.*)\$/sD";
1644 # Match the end of a line for a word that's not followed by whitespace, 1645 # e.g. in the case of 'The Arab al[[Razi]]', 'al' will be matched 1646 $e2 = wfMsgForContent(
'linkprefix');
1648 $useLinkPrefixExtension = $wgContLang->linkPrefixExtension();
1649 if (is_null($this->mTitle)) {
1650 throw new MWException(__METHOD__ .
": \$this->mTitle is null\n");
1652 $nottalk = !$this->mTitle->isTalkPage();
1654 if ($useLinkPrefixExtension) {
1656 if (preg_match($e2,
$s,
$m)) {
1657 $first_prefix =
$m[2];
1659 $first_prefix =
false;
1665 if ($wgContLang->hasVariants()) {
1666 $selflink = $wgContLang->convertLinkToAllVariants($this->mTitle->getPrefixedText());
1668 $selflink = array($this->mTitle->getPrefixedText());
1671 wfProfileOut($fname .
'-setup');
1673 # Loop for each link 1674 for ($k = 0; isset($a[$k]); $k++) {
1676 if ($useLinkPrefixExtension) {
1677 wfProfileIn($fname .
'-prefixhandling');
1678 if (preg_match($e2,
$s,
$m)) {
1685 if ($first_prefix) {
1686 $prefix = $first_prefix;
1687 $first_prefix =
false;
1689 wfProfileOut($fname .
'-prefixhandling');
1692 $might_be_img =
false;
1694 wfProfileIn(
"$fname-e1");
1695 if (preg_match($e1, $line,
$m)) { # page with normal text
or alt
1697 # If we get a ] at the beginning of $m[3] that means we have a link that's something like: 1698 # [[Image:Foo.jpg|[http://example.com desc]]] <- having three ] in a row fucks up, 1699 # the real problem is with the $e1 regex 1702 # Still some problems for cases where the ] is meant to be outside punctuation, 1703 # and no image is in sight. See bug 2095. 1706 substr(
$m[3], 0, 1) ===
']' &&
1707 strpos($text,
'[') !==
false 1710 $m[3] = substr($m[3], 1);
1712 # fix up urlencoded title texts 1713 if (strpos(
$m[1],
'%') !==
false) {
1714 # Should anchors '#' also be rejected? 1715 $m[1] = str_replace(array(
'<',
'>'), array(
'<',
'>'), urldecode(
$m[1]));
1718 } elseif (preg_match($e1_img, $line,
$m)) { # Invalid, but might be an image with
a link
in its caption
1719 $might_be_img =
true;
1721 if (strpos(
$m[1],
'%') !==
false) {
1722 $m[1] = urldecode(
$m[1]);
1725 }
else { # Invalid form; output directly
1726 $s .= $prefix .
'[[' . $line ;
1727 wfProfileOut(
"$fname-e1");
1730 wfProfileOut(
"$fname-e1");
1731 wfProfileIn(
"$fname-misc");
1733 # Don't allow internal links to pages containing 1734 # PROTO: where PROTO is a valid URL protocol; these 1735 # should be external links. 1737 $s .= $prefix .
'[[' . $line ;
1741 # Make subpage if necessary 1748 $noforce = (substr(
$m[1], 0, 1) !=
':');
1750 # Strip off leading ':' 1751 $link = substr($link, 1);
1754 wfProfileOut(
"$fname-misc");
1755 wfProfileIn(
"$fname-title");
1758 $s .= $prefix .
'[[' . $line;
1759 wfProfileOut(
"$fname-title");
1763 $ns = $nt->getNamespace();
1764 $iw = $nt->getInterWiki();
1765 wfProfileOut(
"$fname-title");
1767 if ($might_be_img) { #
if this is actually an invalid link
1768 wfProfileIn(
"$fname-might_be_img");
1769 if ($ns == NS_IMAGE && $noforce) { #but might be an image
1771 while (isset($a[$k + 1])) {
1772 #look at the next 'line' to see if we can close it there 1773 $spliced = array_splice($a, $k + 1, 1);
1774 $next_line = array_shift($spliced);
1775 $m = explode(
']]', $next_line, 3);
1776 if (count(
$m) == 3) {
1777 # the first ]] closes the inner link, the second the image 1779 $text .=
"[[{$m[0]}]]{$m[1]}";
1782 } elseif (count(
$m) == 2) {
1783 #if there's exactly one ]] that's fine, we'll keep looking 1784 $text .=
"[[{$m[0]}]]{$m[1]}";
1786 #if $next_line is invalid too, we need look no further 1787 $text .=
'[[' . $next_line;
1792 # we couldn't find the end of this imageLink, so output it raw 1793 #but don't ignore what might be perfectly normal links in the text we've examined 1795 $s .=
"{$prefix}[[$link|$text";
1796 # note: no $trail, because without an end, there *is* no trail 1797 wfProfileOut(
"$fname-might_be_img");
1800 }
else { #it
's not an image, so output it raw 1801 $s .= "{$prefix}[[$link|$text"; 1802 # note: no $trail, because without an end, there *is* no trail 1803 wfProfileOut("$fname-might_be_img"); 1806 wfProfileOut("$fname-might_be_img"); 1809 $wasblank = ('' == $text); 1814 # Link not escaped by : , create the various objects 1818 wfProfileIn("$fname-interwiki"); 1819 if ($iw && $this->mOptions->getInterwikiMagic() && $nottalk && $wgContLang->getLanguageName($iw)) { 1820 $this->mOutput->addLanguageLink($nt->getFullText()); 1821 $s = rtrim($s . $prefix); 1822 $s .= trim($trail, "\n") == '' ? '': $prefix . $trail; 1823 wfProfileOut("$fname-interwiki"); 1826 wfProfileOut("$fname-interwiki"); 1828 if ($ns == NS_IMAGE) { 1829 wfProfileIn("$fname-image"); 1830 if (!wfIsBadImage($nt->getDBkey(), $this->mTitle)) { 1831 # recursively parse links inside the image caption 1832 # actually, this will parse them in any other parameters, too, 1833 # but it might be hard to fix that, and it doesn't matter ATM
1837 # cloak any absolute URLs inside the image markup, so replaceExternalLinks() won't touch them 1839 $this->mOutput->addImage($nt->getDBkey());
1841 wfProfileOut(
"$fname-image");
1844 # We still need to record the image's presence on the page 1845 $this->mOutput->addImage($nt->getDBkey());
1847 wfProfileOut(
"$fname-image");
1850 if ($ns == NS_CATEGORY) {
1851 wfProfileIn(
"$fname-category");
1852 $s = rtrim(
$s .
"\n"); # bug 87
1860 $sortkey = str_replace(
"\n",
'', $sortkey);
1861 $sortkey = $wgContLang->convertCategoryKey($sortkey);
1862 $this->mOutput->addCategory($nt->getDBkey(), $sortkey);
1868 $s .= trim($prefix . $trail,
"\n") ==
'' ?
'': $prefix . $trail;
1870 wfProfileOut(
"$fname-category");
1875 # Self-link checking 1876 if ($nt->getFragment() ===
'') {
1877 if (in_array($nt->getPrefixedText(), $selflink,
true)) {
1878 $s .= $prefix . $sk->makeSelfLinkObj($nt,
$text,
'', $trail);
1883 # Special and Media are pseudo-namespaces; no pages actually exist in them 1884 if ($ns == NS_MEDIA) {
1885 $link = $sk->makeMediaLinkObj($nt,
$text);
1886 # Cloak with NOPARSE to avoid replacement in replaceExternalLinks 1888 $this->mOutput->addImage($nt->getDBkey());
1893 } elseif ($ns == NS_IMAGE) {
1894 $img =
new Image($nt);
1895 if (
$img->exists()) {
1900 $this->mOutput->addLink($nt);
1906 wfProfileOut($fname);
1919 wfProfileIn(__METHOD__);
1920 if (!is_object($nt)) {
1922 $retVal =
"<!-- ERROR -->{$prefix}{$text}{$trail}";
1924 # Separate the link trail from the rest of the link 1925 list($inside, $trail) = Linker::splitTrail($trail);
1927 if ($nt->isExternal()) {
1928 $nr = array_push($this->mInterwikiLinkHolders[
'texts'], $prefix .
$text . $inside);
1929 $this->mInterwikiLinkHolders[
'titles'][] = $nt;
1930 $retVal =
'<!--IWLINK ' . ($nr - 1) .
"-->{$trail}";
1932 $nr = array_push($this->mLinkHolders[
'namespaces'], $nt->getNamespace());
1933 $this->mLinkHolders[
'dbkeys'][] = $nt->getDBkey();
1934 $this->mLinkHolders[
'queries'][] =
$query;
1935 $this->mLinkHolders[
'texts'][] = $prefix .
$text . $inside;
1936 $this->mLinkHolders[
'titles'][] = $nt;
1938 $retVal =
'<!--LINK ' . ($nr - 1) .
"-->{$trail}";
1941 wfProfileOut(__METHOD__);
1961 list($inside, $trail) = Linker::splitTrail($trail);
1962 $sk = $this->mOptions->getSkin();
1963 $link = $sk->makeKnownLinkObj($nt,
$text,
$query, $inside, $prefix);
1981 return preg_replace(
1983 "{$this->mUniqPrefix}NOPARSE$1",
1994 # Some namespaces don't allow subpages 1995 global $wgNamespacesWithSubpages;
1996 return !empty($wgNamespacesWithSubpages[$this->mTitle->getNamespace()]);
2010 # :Foobar -- override special treatment of prefix (images, language links) 2011 # /Foobar -- convert to CurrentPage/Foobar 2012 # /Foobar/ -- convert to CurrentPage/Foobar, strip the initial / from text 2013 # ../ -- convert to CurrentPage, from CurrentPage/CurrentSubPage 2014 # ../Foobar -- convert to CurrentPage/Foobar, from CurrentPage/CurrentSubPage 2016 $fname =
'Parser::maybeDoSubpageLink';
2017 wfProfileIn($fname);
2023 # Some namespaces don't allow subpages, 2024 # so only perform processing if subpages are allowed 2026 # Look at the first character 2027 if ($target !=
'' && $target[0] ==
'/') {
2028 # / at end means we don't want the slash to be shown 2029 $trailingSlashes = preg_match_all(
'%(/+)$%', $target,
$m);
2030 if ($trailingSlashes) {
2031 $noslash = $target = substr($target, 1, -strlen(
$m[0][0]));
2033 $noslash = substr($target, 1);
2036 $ret = $this->mTitle->getPrefixedText() .
'/' . trim($noslash);
2039 } #
this might be changed
for ugliness reasons
2041 # check for .. subpage backlinks 2044 while (strncmp($nodotdot,
"../", 3) == 0) {
2046 $nodotdot = substr($nodotdot, 3);
2048 if ($dotdotcount > 0) {
2049 $exploded = explode(
'/', $this->mTitle->GetPrefixedText());
2050 if (count($exploded) > $dotdotcount) { # not allowed to go below top level page
2051 $ret = implode(
'/', array_slice($exploded, 0, -$dotdotcount));
2052 # / at the end means don't show full path 2053 if (substr($nodotdot, -1, 1) ==
'/') {
2054 $nodotdot = substr($nodotdot, 0, -1);
2059 $nodotdot = trim($nodotdot);
2060 if ($nodotdot !=
'') {
2061 $ret .=
'/' . $nodotdot;
2068 wfProfileOut($fname);
2079 if (
'' != $this->mLastSection) {
2080 $result =
'</' . $this->mLastSection .
">\n";
2082 $this->mInPre =
false;
2083 $this->mLastSection =
'';
2086 # getCommon() returns the length of the longest common substring 2087 # of both arguments, starting at the beginning of both. 2092 $shorter = strlen($st2);
2093 if ($fl < $shorter) {
2097 for (
$i = 0;
$i < $shorter; ++
$i) {
2098 if ($st1[
$i] != $st2[
$i]) {
2104 # These next three functions open, continue, and close the list 2105 # element appropriate to the prefix character passed into them. 2113 } elseif (
'#' == $char) {
2115 } elseif (
':' == $char) {
2117 } elseif (
';' == $char) {
2119 $this->mDTopen =
true;
2129 if (
'*' == $char ||
'#' == $char) {
2131 } elseif (
':' == $char ||
';' == $char) {
2133 if ($this->mDTopen) {
2137 $this->mDTopen =
true;
2138 return $close .
'<dt>';
2140 $this->mDTopen =
false;
2141 return $close .
'<dd>';
2144 return '<!-- ERR 2 -->';
2150 $text =
'</li></ul>';
2151 } elseif (
'#' == $char) {
2152 $text =
'</li></ol>';
2153 } elseif (
':' == $char) {
2154 if ($this->mDTopen) {
2155 $this->mDTopen =
false;
2156 $text =
'</dt></dl>';
2158 $text =
'</dd></dl>';
2161 return '<!-- ERR 3 -->';
2163 return $text .
"\n";
2175 $fname =
'Parser::doBlockLevels';
2176 wfProfileIn($fname);
2178 # Parsing through the text line by line. The main thing 2179 # happening here is handling of block-level elements p, pre, 2180 # and making lists from lines starting with * # : etc. 2182 $textLines = explode(
"\n",
$text);
2185 $this->mDTopen = $inBlockElem =
false;
2187 $paragraphStack =
false;
2190 $output .= array_shift($textLines);
2192 foreach ($textLines as $oLine) {
2193 $lastPrefixLength = strlen($lastPrefix);
2194 $preCloseMatch = preg_match(
'/<\\/pre/i', $oLine);
2195 $preOpenMatch = preg_match(
'/<pre/i', $oLine);
2196 if (!$this->mInPre) {
2197 # Multiple prefixes may abut each other for nested lists. 2198 $prefixLength = strspn($oLine,
'*#:;');
2199 $pref = substr($oLine, 0, $prefixLength);
2202 $pref2 = str_replace(
';',
':', $pref);
2203 $t = substr($oLine, $prefixLength);
2204 $this->mInPre = !empty($preOpenMatch);
2206 # Don't interpret any other prefixes in preformatted text 2208 $pref = $pref2 =
'';
2213 if ($prefixLength && 0 == strcmp($lastPrefix, $pref2)) {
2214 # Same as the last item, so no need to deal with nesting or opening stuff 2216 $paragraphStack =
false;
2218 if (substr($pref, -1) ==
';') {
2219 # The one nasty exception: definition lists work like this: 2220 # ; title : definition text 2221 # So we check for : in the remainder text to split up the 2222 # title and definition, without b0rking links. 2229 } elseif ($prefixLength || $lastPrefixLength) {
2230 # Either open or close a level... 2231 $commonPrefixLength = $this->
getCommon($pref, $lastPrefix);
2232 $paragraphStack =
false;
2234 while ($commonPrefixLength < $lastPrefixLength) {
2236 --$lastPrefixLength;
2238 if ($prefixLength <= $commonPrefixLength && $commonPrefixLength > 0) {
2241 while ($prefixLength > $commonPrefixLength) {
2242 $char = substr($pref, $commonPrefixLength, 1);
2246 # FIXME: This is dupe of code above 2252 ++$commonPrefixLength;
2254 $lastPrefix = $pref2;
2256 if (0 == $prefixLength) {
2257 wfProfileIn(
"$fname-paragraph");
2258 # No prefix (not in list)--go to paragraph mode 2260 $openmatch = preg_match(
'/(?:<table|<blockquote|<h1|<h2|<h3|<h4|<h5|<h6|<pre|<tr|<p|<ul|<ol|<li|<\\/tr|<\\/td|<\\/th)/iS',
$t);
2261 $closematch = preg_match(
2262 '/(?:<\\/table|<\\/blockquote|<\\/h1|<\\/h2|<\\/h3|<\\/h4|<\\/h5|<\\/h6|' .
2263 '<td|<th|<\\/?div|<hr|<\\/pre|<\\/p|' . $this->mUniqPrefix .
'-pre|<\\/li|<\\/ul|<\\/ol|<\\/?center)/iS',
2266 if ($openmatch
or $closematch) {
2267 $paragraphStack =
false;
2268 #Â TODO bug 5718: paragraph closed 2270 if ($preOpenMatch and !$preCloseMatch) {
2271 $this->mInPre =
true;
2274 $inBlockElem =
false;
2276 $inBlockElem =
true;
2278 } elseif (!$inBlockElem && !$this->mInPre) {
2279 if (
' ' ==
$t[0] and ($this->mLastSection ==
'pre' or trim(
$t) !=
'')) {
2281 if ($this->mLastSection !=
'pre') {
2282 $paragraphStack =
false;
2284 $this->mLastSection =
'pre';
2289 if (
'' == trim(
$t)) {
2290 if ($paragraphStack) {
2291 $output .= $paragraphStack .
'<br />';
2292 $paragraphStack =
false;
2293 $this->mLastSection =
'p';
2295 if ($this->mLastSection !=
'p') {
2297 $this->mLastSection =
'';
2298 $paragraphStack =
'<p>';
2300 $paragraphStack =
'</p><p>';
2304 if ($paragraphStack) {
2306 $paragraphStack =
false;
2307 $this->mLastSection =
'p';
2308 } elseif ($this->mLastSection !=
'p') {
2310 $this->mLastSection =
'p';
2315 wfProfileOut(
"$fname-paragraph");
2318 if ($preCloseMatch && $this->mInPre) {
2319 $this->mInPre =
false;
2321 if ($paragraphStack ===
false) {
2325 while ($prefixLength) {
2329 if (
'' != $this->mLastSection) {
2330 $output .=
'</' . $this->mLastSection .
'>';
2331 $this->mLastSection =
'';
2334 wfProfileOut($fname);
2348 $fname =
'Parser::findColonNoLinks';
2349 wfProfileIn($fname);
2351 $pos = strpos($str,
':');
2352 if ($pos ===
false) {
2354 wfProfileOut($fname);
2358 $lt = strpos($str,
'<');
2359 if ($lt ===
false || $lt > $pos) {
2361 $before = substr($str, 0, $pos);
2362 $after = substr($str, $pos + 1);
2363 wfProfileOut($fname);
2370 $len = strlen($str);
2371 for (
$i = 0;
$i < $len;
$i++) {
2385 $before = substr($str, 0,
$i);
2386 $after = substr($str,
$i + 1);
2387 wfProfileOut($fname);
2394 $colon = strpos($str,
':',
$i);
2395 if ($colon ===
false) {
2397 wfProfileOut($fname);
2400 $lt = strpos($str,
'<',
$i);
2402 if ($lt ===
false || $colon < $lt) {
2404 $before = substr($str, 0, $colon);
2405 $after = substr($str, $colon + 1);
2406 wfProfileOut($fname);
2410 if ($lt ===
false) {
2456 wfDebug(
"Invalid input in $fname; too many close tags\n");
2457 wfProfileOut($fname);
2492 throw new MWException(
"State machine error in $fname");
2496 wfDebug(
"Invalid input in $fname; not enough close tags (stack $stack, state $state)\n");
2499 wfProfileOut($fname);
2510 global $wgContLang, $wgSitename, $wgServer, $wgServerName, $wgScriptPath;
2516 static $varCache = array();
2517 if (wfRunHooks(
'ParserGetVariableValueVarCache', array( &$this, &$varCache ))) {
2518 if (isset($varCache[
$index])) {
2519 return $varCache[
$index];
2524 wfRunHooks(
'ParserGetVariableValueTs', array( &$this, &$ts ));
2527 global $wgLocaltimezone;
2528 if (isset($wgLocaltimezone)) {
2529 $oldtz = getenv(
'TZ');
2530 putenv(
'TZ=' . $wgLocaltimezone);
2532 $localTimestamp = date(
'YmdHis', $ts);
2533 $localMonth = date(
'm', $ts);
2534 $localMonthName = date(
'n', $ts);
2535 $localDay = date(
'j', $ts);
2536 $localDay2 = date(
'd', $ts);
2537 $localDayOfWeek = date(
'w', $ts);
2538 $localWeek = date(
'W', $ts);
2539 $localYear = date(
'Y', $ts);
2540 $localHour = date(
'H', $ts);
2541 if (isset($wgLocaltimezone)) {
2542 putenv(
'TZ=' . $oldtz);
2546 case 'currentmonth':
2547 return $varCache[
$index] = $wgContLang->formatNum(date(
'm', $ts));
2548 case 'currentmonthname':
2549 return $varCache[
$index] = $wgContLang->getMonthName(date(
'n', $ts));
2550 case 'currentmonthnamegen':
2551 return $varCache[
$index] = $wgContLang->getMonthNameGen(date(
'n', $ts));
2552 case 'currentmonthabbrev':
2553 return $varCache[
$index] = $wgContLang->getMonthAbbreviation(date(
'n', $ts));
2555 return $varCache[
$index] = $wgContLang->formatNum(date(
'j', $ts));
2557 return $varCache[
$index] = $wgContLang->formatNum(date(
'd', $ts));
2559 return $varCache[
$index] = $wgContLang->formatNum($localMonth);
2560 case 'localmonthname':
2561 return $varCache[
$index] = $wgContLang->getMonthName($localMonthName);
2562 case 'localmonthnamegen':
2563 return $varCache[
$index] = $wgContLang->getMonthNameGen($localMonthName);
2564 case 'localmonthabbrev':
2565 return $varCache[
$index] = $wgContLang->getMonthAbbreviation($localMonthName);
2567 return $varCache[
$index] = $wgContLang->formatNum($localDay);
2569 return $varCache[
$index] = $wgContLang->formatNum($localDay2);
2571 return $this->mTitle->getText();
2573 return $this->mTitle->getPartialURL();
2574 case 'fullpagename':
2575 return $this->mTitle->getPrefixedText();
2576 case 'fullpagenamee':
2577 return $this->mTitle->getPrefixedURL();
2579 return $this->mTitle->getSubpageText();
2580 case 'subpagenamee':
2581 return $this->mTitle->getSubpageUrlForm();
2582 case 'basepagename':
2583 return $this->mTitle->getBaseText();
2584 case 'basepagenamee':
2585 return wfUrlEncode(str_replace(
' ',
'_', $this->mTitle->getBaseText()));
2586 case 'talkpagename':
2587 if ($this->mTitle->canTalk()) {
2588 $talkPage = $this->mTitle->getTalkPage();
2589 return $talkPage->getPrefixedText();
2594 case 'talkpagenamee':
2595 if ($this->mTitle->canTalk()) {
2596 $talkPage = $this->mTitle->getTalkPage();
2597 return $talkPage->getPrefixedUrl();
2602 case 'subjectpagename':
2603 $subjPage = $this->mTitle->getSubjectPage();
2604 return $subjPage->getPrefixedText();
2605 case 'subjectpagenamee':
2606 $subjPage = $this->mTitle->getSubjectPage();
2607 return $subjPage->getPrefixedUrl();
2612 case 'revisionday2':
2614 case 'revisionmonth':
2616 case 'revisionyear':
2618 case 'revisiontimestamp':
2621 return str_replace(
'_',
' ', $wgContLang->getNsText($this->mTitle->getNamespace()));
2623 return wfUrlencode($wgContLang->getNsText($this->mTitle->getNamespace()));
2625 return $this->mTitle->canTalk() ? str_replace(
'_',
' ', $this->mTitle->getTalkNsText()) :
'';
2627 return $this->mTitle->canTalk() ? wfUrlencode($this->mTitle->getTalkNsText()) :
'';
2628 case 'subjectspace':
2629 return $this->mTitle->getSubjectNsText();
2630 case 'subjectspacee':
2631 return(wfUrlencode($this->mTitle->getSubjectNsText()));
2632 case 'currentdayname':
2633 return $varCache[
$index] = $wgContLang->getWeekdayName(date(
'w', $ts) + 1);
2635 return $varCache[
$index] = $wgContLang->formatNum(date(
'Y', $ts),
true);
2637 return $varCache[
$index] = $wgContLang->time(wfTimestamp(TS_MW, $ts),
false,
false);
2639 return $varCache[
$index] = $wgContLang->formatNum(date(
'H', $ts),
true);
2643 return $varCache[
$index] = $wgContLang->formatNum((
int) date(
'W', $ts));
2645 return $varCache[
$index] = $wgContLang->formatNum(date(
'w', $ts));
2646 case 'localdayname':
2647 return $varCache[
$index] = $wgContLang->getWeekdayName($localDayOfWeek + 1);
2649 return $varCache[
$index] = $wgContLang->formatNum($localYear,
true);
2651 return $varCache[
$index] = $wgContLang->time($localTimestamp,
false,
false);
2653 return $varCache[
$index] = $wgContLang->formatNum($localHour,
true);
2657 return $varCache[
$index] = $wgContLang->formatNum((
int) $localWeek);
2659 return $varCache[
$index] = $wgContLang->formatNum($localDayOfWeek);
2660 case 'numberofarticles':
2661 return $varCache[
$index] = $wgContLang->formatNum(SiteStats::articles());
2662 case 'numberoffiles':
2663 return $varCache[
$index] = $wgContLang->formatNum(SiteStats::images());
2664 case 'numberofusers':
2665 return $varCache[
$index] = $wgContLang->formatNum(SiteStats::users());
2666 case 'numberofpages':
2667 return $varCache[
$index] = $wgContLang->formatNum(SiteStats::pages());
2668 case 'numberofadmins':
2669 return $varCache[
$index] = $wgContLang->formatNum(SiteStats::admins());
2670 case 'numberofedits':
2671 return $varCache[
$index] = $wgContLang->formatNum(SiteStats::edits());
2672 case 'currenttimestamp':
2673 return $varCache[
$index] = wfTimestampNow();
2674 case 'localtimestamp':
2675 return $varCache[
$index] = $localTimestamp;
2676 case 'currentversion':
2677 return $varCache[
$index] = SpecialVersion::getVersion();
2683 return $wgServerName;
2685 return $wgScriptPath;
2686 case 'directionmark':
2687 return $wgContLang->getDirMark();
2688 case 'contentlanguage':
2689 global $wgContLanguageCode;
2690 return $wgContLanguageCode;
2693 if (wfRunHooks(
'ParserGetVariableValueSwitch', array( &$this, &$varCache, &
$index, &
$ret ))) {
2708 $fname =
'Parser::initialiseVariables';
2709 wfProfileIn($fname);
2710 $variableIDs = MagicWord::getVariableIDs();
2712 $this->mVariables = array();
2713 foreach ($variableIDs as
$id) {
2715 $mw->addToArray($this->mVariables, $id);
2717 wfProfileOut($fname);
2738 wfProfileIn(__METHOD__);
2739 $openingBraceStack = array(); #
this array will hold
a stack
of parentheses which
are not closed yet
2740 $lastOpeningBrace = -1; # last not closed parentheses
2742 $validOpeningBraces = implode(
'', array_keys($callbacks));
2746 # Find next opening brace, closing brace or pipe 2747 if ($lastOpeningBrace == -1) {
2748 $currentClosing =
'';
2749 $search = $validOpeningBraces;
2751 $currentClosing = $openingBraceStack[$lastOpeningBrace][
'braceEnd'];
2752 $search = $validOpeningBraces .
'|' . $currentClosing;
2759 } elseif (
$text[
$i] == $currentClosing) {
2761 } elseif (isset($callbacks[
$text[
$i]])) {
2765 # Some versions of PHP have a strcspn which stops on null characters 2766 # Ignore and continue 2775 if ($found ==
'open') {
2776 # found opening brace, let's add it to parentheses stack 2777 $piece = array(
'brace' =>
$text[
$i],
2778 'braceEnd' =>
$rule[
'end'],
2782 # count opening brace characters 2783 $piece[
'count'] = strspn(
$text, $piece[
'brace'], $i);
2784 $piece[
'startAt'] = $piece[
'partStart'] = $i + $piece[
'count'];
2785 $i += $piece[
'count'];
2787 # we need to add to stack only if opening brace count is enough for one of the rules 2788 if ($piece[
'count'] >=
$rule[
'min']) {
2789 $lastOpeningBrace++;
2790 $openingBraceStack[$lastOpeningBrace] = $piece;
2792 } elseif ($found ==
'close') {
2793 # lets check if it is enough characters for closing brace 2794 $maxCount = $openingBraceStack[$lastOpeningBrace][
'count'];
2797 # check for maximum matching characters (if there are 5 closing 2798 # characters, we will probably need only 3 - depending on the rules) 2800 $matchingCallback = null;
2801 $cbType = $callbacks[$openingBraceStack[$lastOpeningBrace][
'brace']];
2802 if ($count > $cbType[
'max']) {
2803 # The specified maximum exists in the callback array, unless the caller 2805 $matchingCount = $cbType[
'max'];
2807 # Count is less than the maximum 2808 # Skip any gaps in the callback array to find the true largest match 2809 # Need to use array_key_exists not isset because the callback can be null 2810 $matchingCount = $count;
2811 while ($matchingCount > 0 && !array_key_exists($matchingCount, $cbType[
'cb'])) {
2816 if ($matchingCount <= 0) {
2820 $matchingCallback = $cbType[
'cb'][$matchingCount];
2822 # let's set a title or last part (if '|' was found) 2823 if (null === $openingBraceStack[$lastOpeningBrace][
'parts']) {
2824 $openingBraceStack[$lastOpeningBrace][
'title'] =
2827 $openingBraceStack[$lastOpeningBrace][
'partStart'],
2828 $i - $openingBraceStack[$lastOpeningBrace][
'partStart']
2831 $openingBraceStack[$lastOpeningBrace][
'parts'][] =
2834 $openingBraceStack[$lastOpeningBrace][
'partStart'],
2835 $i - $openingBraceStack[$lastOpeningBrace][
'partStart']
2839 $pieceStart = $openingBraceStack[$lastOpeningBrace][
'startAt'] - $matchingCount;
2840 $pieceEnd = $i + $matchingCount;
2842 if (is_callable($matchingCallback)) {
2844 'text' => substr(
$text, $pieceStart, $pieceEnd - $pieceStart),
2845 'title' => trim($openingBraceStack[$lastOpeningBrace][
'title']),
2846 'parts' => $openingBraceStack[$lastOpeningBrace][
'parts'],
2847 'lineStart' => (($pieceStart > 0) && (
$text[$pieceStart - 1] ==
"\n")),
2849 # finally we can call a user callback and replace piece of text 2850 $replaceWith = call_user_func($matchingCallback, $cbArgs);
2851 $text = substr(
$text, 0, $pieceStart) . $replaceWith . substr(
$text, $pieceEnd);
2852 $i = $pieceStart + strlen($replaceWith);
2854 # null value for callback means that parentheses should be parsed, but not replaced 2855 $i += $matchingCount;
2858 # reset last opening parentheses, but keep it in case there are unused characters 2859 $piece = array(
'brace' => $openingBraceStack[$lastOpeningBrace][
'brace'],
2860 'braceEnd' => $openingBraceStack[$lastOpeningBrace][
'braceEnd'],
2861 'count' => $openingBraceStack[$lastOpeningBrace][
'count'],
2864 'startAt' => $openingBraceStack[$lastOpeningBrace][
'startAt']);
2865 $openingBraceStack[$lastOpeningBrace--] = null;
2867 if ($matchingCount < $piece[
'count']) {
2868 $piece[
'count'] -= $matchingCount;
2869 $piece[
'startAt'] -= $matchingCount;
2870 $piece[
'partStart'] = $piece[
'startAt'];
2871 # do we still qualify for any callback with remaining count? 2872 $currentCbList = $callbacks[$piece[
'brace']][
'cb'];
2873 while ($piece[
'count']) {
2874 if (array_key_exists($piece[
'count'], $currentCbList)) {
2875 $lastOpeningBrace++;
2876 $openingBraceStack[$lastOpeningBrace] = $piece;
2882 } elseif ($found ==
'pipe') {
2883 # lets set a title if it is a first separator, or next part otherwise 2884 if (null === $openingBraceStack[$lastOpeningBrace][
'parts']) {
2885 $openingBraceStack[$lastOpeningBrace][
'title'] =
2888 $openingBraceStack[$lastOpeningBrace][
'partStart'],
2889 $i - $openingBraceStack[$lastOpeningBrace][
'partStart']
2891 $openingBraceStack[$lastOpeningBrace][
'parts'] = array();
2893 $openingBraceStack[$lastOpeningBrace][
'parts'][] =
2896 $openingBraceStack[$lastOpeningBrace][
'partStart'],
2897 $i - $openingBraceStack[$lastOpeningBrace][
'partStart']
2900 $openingBraceStack[$lastOpeningBrace][
'partStart'] = ++
$i;
2904 wfProfileOut(__METHOD__);
2925 # Prevent too big inclusions 2926 if (strlen(
$text) > $this->mOptions->getMaxIncludeSize()) {
2930 $fname = __METHOD__ ;
2931 wfProfileIn($fname);
2933 # This function is called recursively. To keep track of arguments we need a stack: 2934 array_push($this->mArgStack, $args);
2936 $braceCallbacks = array();
2938 $braceCallbacks[2] = array( &$this,
'braceSubstitution' );
2940 if ($this->mOutputType !=
OT_MSG) {
2941 $braceCallbacks[3] = array( &$this,
'argSubstitution' );
2943 if ($braceCallbacks) {
2947 'cb' => $braceCallbacks,
2948 'min' => $argsOnly ? 3 : 2,
2949 'max' => isset($braceCallbacks[3]) ? 3 : 2,
2953 'cb' => array(2 => null),
2960 array_pop($this->mArgStack);
2962 wfProfileOut($fname);
2973 $fname =
'Parser::variableSubstitution';
2974 $varname = $wgContLang->lc($matches[1]);
2975 wfProfileIn($fname);
2977 if ($this->mOutputType ==
OT_WIKI) {
2978 # Do only magic variables prefixed by SUBST 2980 if (!$mwSubst->matchStartAndRemove($varname)) {
2983 # Note that if we don't substitute the variable below, 2984 # we don't remove the {{subst:}} magic word, in case 2985 # it is a template rather than a magic variable. 2987 if (!$skip && array_key_exists($varname, $this->mVariables)) {
2988 $id = $this->mVariables[$varname];
2989 # Now check if we did really match, case sensitive or not 2991 if ($mw->match($matches[1])) {
2993 $this->mOutput->mContainsOldMagic =
true;
2995 $text = $matches[0];
2998 $text = $matches[0];
3000 wfProfileOut($fname);
3008 $assocArgs = array();
3010 foreach ($args as $arg) {
3011 $eqpos = strpos($arg,
'=');
3012 if ($eqpos ===
false) {
3013 $assocArgs[
$index++] = $arg;
3015 $name = trim(substr($arg, 0, $eqpos));
3016 $value = trim(substr($arg, $eqpos + 1));
3017 if ($value ===
false) {
3020 if (
$name !==
false) {
3021 $assocArgs[
$name] = $value;
3042 global $wgContLang, $wgLang, $wgAllowDisplayTitle, $wgNonincludableNamespaces;
3043 $fname = __METHOD__ ;
3044 wfProfileIn($fname);
3045 wfProfileIn(__METHOD__ .
'-setup');
3048 $found =
false; #
$text has been filled
3049 $nowiki =
false; # wiki markup
in $text should be escaped
3050 $noparse =
false; # Unsafe HTML tags should not be stripped, etc.
3051 $noargs =
false; # Don
't replace triple-brace arguments in $text 3052 $replaceHeadings = false; # Make the edit section links go to the template not the article 3053 $headingOffset = 0; # Skip headings when number, to account for those that weren't transcluded.
3054 $isHTML =
false; #
$text is HTML, armour it against wikitext transformation
3055 $forceRawInterwiki =
false; # Force interwiki transclusion to be done
in raw mode not rendered
3057 # Title object, where $text came from 3063 # $part1 is the bit before the first |, and must contain only title characters 3064 # $args is a list of arguments, starting from index 0, not including $part1 3066 $titleText = $part1 = $piece[
'title'];
3067 # If the third subpattern matched anything, it will start with | 3069 if (null == $piece[
'parts']) {
3071 if ($replaceWith != $piece[
'text']) {
3072 $text = $replaceWith;
3079 $args = (null == $piece[
'parts']) ? array() : $piece[
'parts'];
3080 wfProfileOut(__METHOD__ .
'-setup');
3083 wfProfileIn(__METHOD__ .
'-modifiers');
3086 if ($mwSubst->matchStartAndRemove($part1) xor $this->ot[
'wiki']) {
3087 # One of two possibilities is true: 3088 # 1) Found SUBST but not in the PST phase 3089 # 2) Didn't find SUBST and in the PST phase 3090 # In either case, return without further processing 3091 $text = $piece[
'text'];
3098 # MSG, MSGNW and RAW 3102 if ($mwMsgnw->matchStartAndRemove($part1)) {
3105 # Remove obsolete MSG: 3107 $mwMsg->matchStartAndRemove($part1);
3112 if ($mwRaw->matchStartAndRemove($part1)) {
3113 $forceRawInterwiki =
true;
3116 wfProfileOut(__METHOD__ .
'-modifiers');
3123 wfProfileIn(__METHOD__ .
'-pfunc');
3125 $colonPos = strpos($part1,
':');
3126 if ($colonPos !==
false) {
3127 # Case sensitive functions 3128 $function = substr($part1, 0, $colonPos);
3129 if (isset($this->mFunctionSynonyms[1][$function])) {
3130 $function = $this->mFunctionSynonyms[1][$function];
3132 # Case insensitive functions 3133 $function = strtolower($function);
3134 if (isset($this->mFunctionSynonyms[0][$function])) {
3135 $function = $this->mFunctionSynonyms[0][$function];
3141 $funcArgs = array_map(
'trim', $args);
3142 $funcArgs = array_merge(array( &$this, trim(substr($part1, $colonPos + 1)) ), $funcArgs);
3143 $result = call_user_func_array($this->mFunctionHooks[$function], $funcArgs);
3164 wfProfileOut(__METHOD__ .
'-pfunc');
3167 # Template table test 3169 # Did we encounter this template already? If yes, it is in the cache 3170 # and we need to check for loops. 3171 if (!$found && isset($this->mTemplates[$piece[
'title']])) {
3174 # Infinite loop test 3175 if (isset($this->mTemplatePath[$part1])) {
3179 $text = $linestart .
3180 "[[$part1]]<!-- WARNING: template loop detected -->";
3181 wfDebug(__METHOD__ .
": template loop broken at '$part1'\n");
3183 # set $text to cached message. 3184 $text = $linestart . $this->mTemplates[$piece[
'title']];
3185 #treat title for cached page the same as others 3189 if ($subpage !==
'') {
3190 $ns = $this->mTitle->getNamespace();
3194 $titleText = $title->getPrefixedText();
3196 $replaceHeadings =
true;
3200 # Load from database 3202 wfProfileIn(__METHOD__ .
'-loadtpl');
3204 # declaring $subpage directly in the function call 3205 # does not work correctly with references and breaks 3206 # {{/subpage}}-style inclusions 3209 if ($subpage !==
'') {
3210 $ns = $this->mTitle->getNamespace();
3215 if (!is_null($title)) {
3216 $titleText = $title->getPrefixedText();
3217 # Check for language variants if the template is not found 3218 if ($wgContLang->hasVariants() && $title->getArticleID() == 0) {
3219 $wgContLang->findVariantLink($part1, $title);
3222 if (!$title->isExternal()) {
3223 if ($title->getNamespace() ==
NS_SPECIAL && $this->mOptions->getAllowSpecialInclusion() && $this->ot[
'html']) {
3224 $text = SpecialPage::capturePath($title);
3225 if (is_string(
$text)) {
3232 } elseif ($wgNonincludableNamespaces && in_array($title->getNamespace(), $wgNonincludableNamespaces)) {
3234 wfDebug(
"$fname: template inclusion denied for " . $title->getPrefixedDBkey());
3237 if ($articleContent !==
false) {
3239 $text = $articleContent;
3240 $replaceHeadings =
true;
3244 # If the title is valid but undisplayable, make a link to it 3245 if (!$found && ($this->ot[
'html'] || $this->ot[
'pre'])) {
3246 $text =
"[[:$titleText]]";
3249 } elseif ($title->isTrans()) {
3251 if ($this->ot[
'html'] && !$forceRawInterwiki) {
3257 $replaceHeadings =
true;
3262 # Template cache array insertion 3263 # Use the original $piece['title'] not the mangled $part1, so that 3264 # modifiers such as RAW: produce separate cache entries 3269 $this->mTemplates[$piece[
'title']] =
$text;
3274 wfProfileOut(__METHOD__ .
'-loadtpl');
3278 # Error, oversize inclusion 3279 $text = $linestart .
3280 "[[$titleText]]<!-- WARNING: template omitted, pre-expand include size too large -->";
3285 # Recursive parsing, escaping and link table handling 3286 # Only for HTML output 3287 if ($nowiki && $found && ($this->ot[
'html'] || $this->ot[
'pre'])) {
3289 } elseif (!$this->ot[
'msg'] && $found) {
3291 $assocArgs = array();
3293 # Clean up argument array 3294 $assocArgs = self::createAssocArgs($args);
3295 # Add a new element to the templace recursion path 3296 $this->mTemplatePath[$part1] = 1;
3300 # If there are any <onlyinclude> tags, only include them 3301 if (in_string(
'<onlyinclude>',
$text) && in_string(
'</onlyinclude>',
$text)) {
3303 StringUtils::delimiterReplaceCallback(
3306 array( &$replacer,
'replace' ),
3309 $text = $replacer->output;
3311 # Remove <noinclude> sections and <includeonly> tags 3312 $text = StringUtils::delimiterReplace(
'<noinclude>',
'</noinclude>',
'',
$text);
3313 $text = strtr(
$text, array(
'<includeonly>' =>
'' ,
'</includeonly>' =>
'' ));
3315 if ($this->ot[
'html'] || $this->ot[
'pre']) {
3316 # Strip <nowiki>, <pre>, etc. 3318 if ($this->ot[
'html']) {
3320 } elseif ($this->ot[
'pre'] && $this->mOptions->getRemoveComments()) {
3326 # If the template begins with a table or block-level 3327 # element, it should be treated as beginning a new line. 3328 if (!$piece[
'lineStart'] && preg_match(
'/^(?:{\\||:|;|#|\*)/',
$text)) {
3331 } elseif (!$noargs) {
3332 # $noparse and !$noargs 3333 # Just replace the arguments, not any double-brace items 3334 # This is used for rendered interwiki transclusion 3338 # Prune lower levels off the recursion check path 3339 $this->mTemplatePath = $lastPathLevel;
3342 # Error, oversize inclusion 3343 $text = $linestart .
3344 "[[$titleText]]<!-- WARNING: template omitted, post-expand include size too large -->";
3350 wfProfileOut($fname);
3351 return $piece[
'text'];
3353 wfProfileIn(__METHOD__ .
'-placeholders');
3355 # Replace raw HTML by a placeholder 3356 # Add a blank line preceding, to prevent it from mucking up 3357 # immediately preceding headings 3360 # replace ==section headers== 3361 # XXX this needs to go away once we have a better parser. 3362 if (!$this->ot[
'wiki'] && !$this->ot[
'pre'] && $replaceHeadings) {
3363 if (!is_null($title)) {
3364 $encodedname = base64_encode($title->getPrefixedDBkey());
3366 $encodedname = base64_encode(
"");
3369 '/(^={1,6}.*?={1,6}\s*?$)/m',
3372 PREG_SPLIT_DELIM_CAPTURE
3375 $nsec = $headingOffset;
3376 for (
$i = 0;
$i < count(
$m);
$i += 2) {
3378 if (!isset(
$m[
$i + 1]) ||
$m[
$i + 1] ==
"") {
3382 if (strstr($hl,
"<!--MWTEMPLATESECTION")) {
3387 preg_match(
'/^(={1,6})(.*?)(={1,6})\s*?$/m', $hl, $m2);
3388 $text .= $m2[1] . $m2[2] .
"<!--MWTEMPLATESECTION=" 3389 . $encodedname .
"&" . base64_encode(
"$nsec") .
"-->" . $m2[3];
3395 wfProfileOut(__METHOD__ .
'-placeholders');
3398 # Prune lower levels off the recursion check path 3399 $this->mTemplatePath = $lastPathLevel;
3402 wfProfileOut($fname);
3403 return $piece[
'text'];
3405 wfProfileOut($fname);
3418 $rev = Revision::newFromTitle(
$title);
3419 $this->mOutput->addTemplate(
$title,
$title->getArticleID());
3421 $text = $rev->getText();
3422 } elseif (
$title->getNamespace() == NS_MEDIAWIKI) {
3433 if (
$text ===
false) {
3447 global $wgEnableScaryTranscluding;
3449 if (!$wgEnableScaryTranscluding) {
3450 return wfMsg(
'scarytranscludedisabled');
3455 if (strlen(
$url) > 255) {
3456 return wfMsg(
'scarytranscludetoolong');
3463 global $wgTranscludeCacheExpiry;
3464 $dbr = wfGetDB(DB_SLAVE);
3465 $obj = $dbr->selectRow(
3467 array(
'tc_time',
'tc_contents'),
3468 array(
'tc_url' =>
$url)
3471 $time = $obj->tc_time;
3472 $text = $obj->tc_contents;
3473 if (
$time && time() <
$time + $wgTranscludeCacheExpiry) {
3480 return wfMsg(
'scarytranscludefailed',
$url);
3483 $dbw = wfGetDB(DB_MASTER);
3484 $dbw->replace(
'transcache', array(
'tc_url'), array(
3486 'tc_time' => time(),
3487 'tc_contents' =>
$text));
3498 $arg = trim($matches[
'title']);
3499 $text = $matches[
'text'];
3500 $inputArgs = end($this->mArgStack);
3502 if (array_key_exists($arg, $inputArgs)) {
3503 $text = $inputArgs[$arg];
3505 null != $matches[
'parts'] && count($matches[
'parts']) > 0) {
3506 $text = $matches[
'parts'][0];
3509 $text = $matches[
'text'] .
3510 '<!-- WARNING: argument omitted, expansion size too large -->';
3525 if ($this->mIncludeSizes[
$type] +
$size > $this->mOptions->getMaxIncludeSize()) {
3538 # if the string __NOGALLERY__ (not case-sensitive) occurs in the HTML, 3541 $this->mOutput->mNoGallery = $mw->matchAndRemove(
$text) ;
3549 # if the string __NOTOC__ (not case-sensitive) occurs in the HTML, 3552 if ($mw->matchAndRemove(
$text)) {
3553 $this->mShowToc =
false;
3557 if ($mw->match(
$text)) {
3558 $this->mShowToc =
true;
3559 $this->mForceTocPosition =
true;
3562 $text = $mw->replace(
'<!--MWTOC-->',
$text, 1);
3586 global $wgMaxTocLevel, $wgContLang;
3588 $doNumberHeadings = $this->mOptions->getNumberHeadings();
3589 if (!$this->mTitle->quickUserCan(
'edit')) {
3592 $showEditLink = $this->mOptions->getEditSection();
3595 # Inhibit editsection links if requested in the page 3597 if ($esw->matchAndRemove(
$text)) {
3601 # Get all headlines for numbering them and adding funky stuff like [edit] 3602 # links - this is for later, but we need the number of headlines right now 3604 $numMatches = preg_match_all(
'/<H(?P<level>[1-6])(?P<attrib>.*?' .
'>)(?P<header>.*?)<\/H[1-6] *>/i',
$text, $matches);
3606 # if there are fewer than 4 headlines in the article, do not show TOC 3607 # unless it's been explicitly enabled. 3608 $enoughToc = $this->mShowToc &&
3609 (($numMatches >= 4) || $this->mForceTocPosition);
3611 # Allow user to stipulate that a page should have a "new section" 3612 # link added via __NEWSECTIONLINK__ 3614 if ($mw->matchAndRemove(
$text)) {
3615 $this->mOutput->setNewSection(
true);
3618 # if the string __FORCETOC__ (not case-sensitive) occurs in the HTML, 3619 # override above conditions and always show TOC above first header 3621 if ($mw->matchAndRemove(
$text)) {
3622 $this->mShowToc =
true;
3626 # Never ever show TOC if no headers 3627 if ($numMatches < 1) {
3631 # We need this to perform operations on the HTML 3632 $sk = $this->mOptions->getSkin();
3636 $sectionCount = 0; # headlineCount excluding
template sections
3638 # Ugh .. the TOC should have neat indentation levels which can be 3639 # passed to the skin functions. These are determined here 3643 $sublevelCount = array();
3644 $levelCount = array();
3651 foreach ($matches[3] as $headline) {
3653 $templatetitle =
'';
3654 $templatesection = 0;
3657 if (preg_match(
"/<!--MWTEMPLATESECTION=([^&]+)&([^_]+)-->/", $headline, $mat)) {
3659 $templatetitle = base64_decode($mat[1]);
3660 $templatesection = 1 + (int) base64_decode($mat[2]);
3661 $headline = preg_replace(
"/<!--MWTEMPLATESECTION=([^&]+)&([^_]+)-->/",
"", $headline);
3665 $prevlevel = $level;
3666 $prevtoclevel = $toclevel;
3668 $level = $matches[1][$headlineCount];
3670 if ($doNumberHeadings || $enoughToc) {
3671 if ($level > $prevlevel) {
3672 # Increase TOC level 3674 $sublevelCount[$toclevel] = 0;
3675 if ($toclevel < $wgMaxTocLevel) {
3676 $toc .= $sk->tocIndent();
3678 } elseif ($level < $prevlevel && $toclevel > 1) {
3679 # Decrease TOC level, find level to jump to 3681 if ($toclevel == 2 && $level <= $levelCount[1]) {
3682 # Can only go down to level 1 3685 for (
$i = $toclevel;
$i > 0;
$i--) {
3686 if ($levelCount[
$i] == $level) {
3687 # Found last matching level 3690 } elseif ($levelCount[
$i] < $level) {
3691 # Found first matching level below current level 3697 if ($toclevel < $wgMaxTocLevel) {
3698 $toc .= $sk->tocUnindent($prevtoclevel - $toclevel);
3701 # No change in level, end TOC line 3702 if ($toclevel < $wgMaxTocLevel) {
3703 $toc .= $sk->tocLineEnd();
3707 $levelCount[$toclevel] = $level;
3709 # count number of headlines for each level 3710 @$sublevelCount[$toclevel]++;
3712 for (
$i = 1;
$i <= $toclevel;
$i++) {
3713 if (!empty($sublevelCount[
$i])) {
3717 $numbering .= $wgContLang->formatNum($sublevelCount[$i]);
3723 # The canonized header is a version of the header text safe to use for links 3724 # Avoid insertion of weird stuff like <math> by expanding the relevant sections 3725 $canonized_headline = $this->mStripState->unstripBoth($headline);
3727 # Remove link placeholders by the link text. 3728 # <!--LINK number--> 3730 # link text with suffix 3731 $canonized_headline = preg_replace_callback(
3732 '/<!--LINK ([0-9]*)-->/',
3734 return $this->mLinkHolders[
'texts'][$hit[1]];
3738 $canonized_headline = preg_replace_callback(
3739 '/<!--IWLINK ([0-9]*)-->/',
3741 return $this->mInterwikiLinkHolders[
'texts'][$hit[1]];
3747 $canonized_headline = preg_replace(
'/<.*?' .
'>/',
'', $canonized_headline);
3748 $tocline = trim($canonized_headline);
3749 # Save headline for section edit hint before it's escaped 3750 $headline_hint = trim($canonized_headline);
3752 $refers[$headlineCount] = $canonized_headline;
3754 # count how many in assoc. array so we can track dupes in anchors 3755 isset($refers[$canonized_headline]) ? $refers[$canonized_headline]++ : $refers[$canonized_headline] = 1;
3756 $refcount[$headlineCount] = $refers[$canonized_headline];
3758 # Don't number the heading if it is the only one (looks silly) 3759 if ($doNumberHeadings && count($matches[3]) > 1) {
3760 # the two are different if the line contains a link 3761 $headline = $numbering .
' ' . $headline;
3764 # Create the anchor for linking from the TOC to the section 3765 $anchor = $canonized_headline;
3766 if ($refcount[$headlineCount] > 1) {
3767 $anchor .=
'_' . $refcount[$headlineCount];
3769 if ($enoughToc && (!isset($wgMaxTocLevel) || $toclevel < $wgMaxTocLevel)) {
3770 $toc .= $sk->tocLine($anchor, $tocline, $numbering, $toclevel);
3772 # give headline the correct <h#> tag 3773 if ($showEditLink && (!$istemplate || $templatetitle !==
"")) {
3775 $editlink = $sk->editSectionLinkForOther($templatetitle, $templatesection);
3777 $editlink = $sk->editSectionLink($this->mTitle, $sectionCount + 1, $headline_hint);
3782 $head[$headlineCount] = $sk->makeHeadline($level, $matches[
'attrib'][$headlineCount], $anchor, $headline, $editlink);
3791 if ($toclevel < $wgMaxTocLevel) {
3792 $toc .= $sk->tocUnindent($toclevel - 1);
3794 $toc = $sk->tocList($toc);
3797 # split up and insert constructed headlines 3799 $blocks = preg_split(
'/<H[1-6].*?' .
'>.*?<\/H[1-6]>/i',
$text);
3802 foreach ($blocks as $block) {
3803 if ($showEditLink && $headlineCount > 0 &&
$i == 0 && $block !=
"\n") {
3804 # This is the [edit] link that appears for the top block of text when 3805 # section editing is enabled 3807 # Disabled because it broke block formatting 3808 # For example, a bullet point in the top line 3809 # $full .= $sk->editSectionLink(0); 3812 if ($enoughToc && !
$i && $isMain && !$this->mForceTocPosition) {
3813 # Top anchor now in skin 3814 $full = $full . $toc;
3817 if (!empty($head[
$i])) {
3822 if ($this->mForceTocPosition) {
3823 return str_replace(
'<!--MWTOC-->', $toc, $full);
3855 $text = str_replace(array_keys($pairs), array_values($pairs),
$text);
3868 global $wgContLang, $wgLocaltimezone;
3875 if (isset($wgLocaltimezone)) {
3876 $oldtz = getenv(
'TZ');
3877 putenv(
'TZ=' . $wgLocaltimezone);
3879 $d = $wgContLang->timeanddate(date(
'YmdHis'),
false,
false) .
3880 ' (' . date(
'T') .
')';
3881 if (isset($wgLocaltimezone)) {
3882 putenv(
'TZ=' . $oldtz);
3885 # Variable replacement 3886 # Because mOutputType is OT_WIKI, this will only process {{subst:xxx}} type tags 3889 # Strip out <nowiki> etc. added via replaceVariables 3896 '~~~~' =>
"$sigText $d",
3900 # Context links: [[|name]] and [[name (context)|]] 3902 global $wgLegalTitleChars;
3903 $tc =
"[$wgLegalTitleChars]";
3904 $nc =
'[ _0-9A-Za-z\x80-\xff]'; # Namespaces can use non-ascii!
3906 $p1 =
"/\[\[(:?$nc+:|:|)($tc+?)( \\($tc+\\))\\|]]/"; # [[ns:page (context)|]]
3907 $p3 =
"/\[\[(:?$nc+:|:|)($tc+?)( \\($tc+\\)|)(, $tc+|)\\|]]/"; # [[ns:page (context), context|]]
3908 $p2 =
"/\[\[\\|($tc+)]]/"; # [[|page]]
3910 # try $p1 first, to turn "[[A, B (C)|]]" into "[[A, B (C)|A, B]]" 3911 $text = preg_replace($p1,
'[[\\1\\2\\3|\\2]]',
$text);
3912 $text = preg_replace($p3,
'[[\\1\\2\\3\\4|\\2]]',
$text);
3914 $t = $this->mTitle->getText();
3916 if (preg_match(
"/^($nc+:|)$tc+?( \\($tc+\\))$/",
$t,
$m)) {
3917 $text = preg_replace($p2,
"[[$m[1]\\1$m[2]|\\1]]",
$text);
3918 } elseif (preg_match(
"/^($nc+:|)$tc+?(, $tc+|)$/",
$t,
$m) &&
'' !=
"$m[1]$m[2]") {
3919 $text = preg_replace($p2,
"[[$m[1]\\1$m[2]|\\1]]",
$text);
3921 # if there's no context, don't bother duplicating the title 3925 # Trim trailing whitespace 3941 $username =
$user->getName();
3942 $nickname =
$user->getOption(
'nickname');
3943 $nickname = $nickname ===
'' ? $username : $nickname;
3945 if (
$user->getBoolOption(
'fancysig') !==
false) {
3946 # Sig. might contain markup; validate this 3948 # Validated; clean up (if needed) and return it 3949 return $this->
cleanSig($nickname,
true);
3951 # Failed to validate; fall back to the default 3952 $nickname = $username;
3953 wfDebug(
"Parser::getUserSig: $username has bad XML tags in signature.\n");
3960 # If we're still here, make it a link to the user page 3961 $userpage =
$user->getUserPage();
3962 return(
'[[' . $userpage->getPrefixedText() .
'|' . wfEscapeWikiText($nickname) .
']]');
3973 return(wfIsWellFormedXmlFragment(
$text) ?
$text :
false);
3992 $substRegex =
'/\{\{(?!(?:' . $substWord->getBaseRegex() .
'))/x' . $substWord->getRegexCase();
3993 $substText =
'{{' . $substWord->getSynonym(0);
3995 $text = preg_replace($substRegex, $substText,
$text);
4040 static $executing =
false;
4042 $fname =
"Parser::transformMsg";
4044 # Guard against infinite recursion 4050 wfProfileIn($fname);
4052 if ($wgTitle && !($wgTitle instanceof FakeTitle)) {
4053 $this->mTitle = $wgTitle;
4063 wfProfileOut($fname);
4085 $oldVal = isset($this->mTagHooks[
$tag]) ? $this->mTagHooks[
$tag] : null;
4086 $this->mTagHooks[
$tag] = $callback;
4117 $oldVal = isset($this->mFunctionHooks[
$id]) ? $this->mFunctionHooks[
$id] : null;
4118 $this->mFunctionHooks[
$id] = $callback;
4120 # Add to function cache 4123 throw new MWException(
'Parser::setFunctionHook() expecting a magic word identifier.');
4126 $synonyms = $mw->getSynonyms();
4127 $sensitive = intval($mw->isCaseSensitive());
4129 foreach ($synonyms as $syn) {
4132 $syn = strtolower($syn);
4138 # Remove trailing colon 4139 if (substr($syn, -1, 1) ==
':') {
4140 $syn = substr($syn, 0, -1);
4142 $this->mFunctionSynonyms[$sensitive][$syn] =
$id;
4154 return array_keys($this->mFunctionHooks);
4171 $fname =
'Parser::replaceLinkHolders';
4172 wfProfileIn($fname);
4176 $sk = $this->mOptions->getSkin();
4177 $linkCache = &LinkCache::singleton();
4179 if (!empty($this->mLinkHolders[
'namespaces'])) {
4180 wfProfileIn($fname .
'-check');
4181 $dbr = wfGetDB(DB_SLAVE);
4182 $page = $dbr->tableName(
'page');
4183 $threshold = $wgUser->getOption(
'stubthreshold');
4186 asort($this->mLinkHolders[
'namespaces']);
4191 foreach ($this->mLinkHolders[
'namespaces'] as
$key => $ns) {
4193 $title = $this->mLinkHolders[
'titles'][
$key];
4195 # Skip invalid entries. 4196 # Result will be ugly, but prevents crash. 4200 $pdbk = $pdbks[
$key] =
$title->getPrefixedDBkey();
4202 # Check if it's a static known link, e.g. interwiki 4203 if (
$title->isAlwaysKnown()) {
4204 $colours[$pdbk] = 1;
4205 } elseif ((
$id = $linkCache->getGoodLinkID($pdbk)) != 0) {
4206 $colours[$pdbk] = 1;
4208 } elseif ($linkCache->isBadLink($pdbk)) {
4209 $colours[$pdbk] = 0;
4211 # Not in the link cache, add it to the query 4214 $query =
"SELECT page_id, page_namespace, page_title";
4215 if ($threshold > 0) {
4216 $query .=
', page_len, page_is_redirect';
4218 $query .=
" FROM $page WHERE (page_namespace=$ns AND page_title IN(";
4221 $query .=
")) OR (page_namespace=$ns AND page_title IN(";
4226 $query .= $dbr->addQuotes($this->mLinkHolders[
'dbkeys'][
$key]);
4237 # Fetch data and form into an associative array 4238 # non-existent = broken 4241 while (
$s = $dbr->fetchObject(
$res)) {
4243 $pdbk =
$title->getPrefixedDBkey();
4244 $linkCache->addGoodLinkObj(
$s->page_id,
$title);
4245 $this->mOutput->addLink(
$title,
$s->page_id);
4247 if ($threshold > 0) {
4249 if (
$s->page_is_redirect ||
$s->page_namespace != 0 ||
$size >= $threshold) {
4250 $colours[$pdbk] = 1;
4252 $colours[$pdbk] = 2;
4255 $colours[$pdbk] = 1;
4259 wfProfileOut($fname .
'-check');
4261 # Do a second query for different language variants of links and categories 4262 if ($wgContLang->hasVariants()) {
4263 $linkBatch =
new LinkBatch();
4264 $variantMap = array();
4265 $categoryMap = array();
4266 $varCategories = array();
4268 $categories = $this->mOutput->getCategoryLinks();
4271 foreach ($this->mLinkHolders[
'namespaces'] as
$key => $ns) {
4272 $title = $this->mLinkHolders[
'titles'][
$key];
4277 $pdbk =
$title->getPrefixedDBkey();
4278 $titleText =
$title->getText();
4281 $allTextVariants = $wgContLang->convertLinkToAllVariants($titleText);
4284 if (!isset($colours[$pdbk])) {
4285 foreach ($allTextVariants as $textVariant) {
4286 if ($textVariant != $titleText) {
4288 if (is_null($variantTitle)) {
4291 $linkBatch->addObj($variantTitle);
4292 $variantMap[$variantTitle->getPrefixedDBkey()][] =
$key;
4299 foreach ($categories as $category) {
4300 $variants = $wgContLang->convertLinkToAllVariants($category);
4301 foreach ($variants as $variant) {
4302 if ($variant != $category) {
4304 if (is_null($variantTitle)) {
4307 $linkBatch->addObj($variantTitle);
4308 $categoryMap[$variant] = $category;
4314 if (!$linkBatch->isEmpty()) {
4316 $titleClause = $linkBatch->constructSet(
'page', $dbr);
4318 $variantQuery =
"SELECT page_id, page_namespace, page_title";
4319 if ($threshold > 0) {
4320 $variantQuery .=
', page_len, page_is_redirect';
4323 $variantQuery .=
" FROM $page WHERE $titleClause";
4325 $variantQuery .=
' FOR UPDATE';
4328 $varRes = $dbr->query($variantQuery, $fname);
4331 while (
$s = $dbr->fetchObject($varRes)) {
4333 $varPdbk = $variantTitle->getPrefixedDBkey();
4334 $vardbk = $variantTitle->getDBkey();
4336 $holderKeys = array();
4337 if (isset($variantMap[$varPdbk])) {
4338 $holderKeys = $variantMap[$varPdbk];
4339 $linkCache->addGoodLinkObj(
$s->page_id, $variantTitle);
4340 $this->mOutput->addLink($variantTitle,
$s->page_id);
4344 foreach ($holderKeys as
$key) {
4345 $title = $this->mLinkHolders[
'titles'][
$key];
4350 $pdbk =
$title->getPrefixedDBkey();
4352 if (!isset($colours[$pdbk])) {
4354 $this->mLinkHolders[
'titles'][
$key] = $variantTitle;
4355 $this->mLinkHolders[
'dbkeys'][
$key] = $variantTitle->getDBkey();
4358 $pdbks[
$key] = $varPdbk;
4359 if ($threshold > 0) {
4361 if (
$s->page_is_redirect ||
$s->page_namespace != 0 ||
$size >= $threshold) {
4362 $colours[$varPdbk] = 1;
4364 $colours[$varPdbk] = 2;
4367 $colours[$varPdbk] = 1;
4373 if (isset($categoryMap[$vardbk])) {
4374 $oldkey = $categoryMap[$vardbk];
4375 if ($oldkey != $vardbk) {
4376 $varCategories[$oldkey] = $vardbk;
4382 if (count($varCategories) > 0) {
4384 $originalCats = $this->mOutput->getCategories();
4385 foreach ($originalCats as $cat => $sortkey) {
4387 if (array_key_exists($cat, $varCategories)) {
4388 $newCats[$varCategories[$cat]] = $sortkey;
4390 $newCats[$cat] = $sortkey;
4393 $this->mOutput->setCategoryLinks($newCats);
4398 # Construct search and replace arrays 4399 wfProfileIn($fname .
'-construct');
4400 $replacePairs = array();
4401 foreach ($this->mLinkHolders[
'namespaces'] as
$key => $ns) {
4402 $pdbk = $pdbks[
$key];
4403 $searchkey =
"<!--LINK $key-->";
4404 $title = $this->mLinkHolders[
'titles'][
$key];
4405 if (empty($colours[$pdbk])) {
4406 $linkCache->addBadLinkObj(
$title);
4407 $colours[$pdbk] = 0;
4408 $this->mOutput->addLink(
$title, 0);
4409 $replacePairs[$searchkey] = $sk->makeBrokenLinkObj(
4411 $this->mLinkHolders[
'texts'][
$key],
4412 $this->mLinkHolders[
'queries'][$key]
4414 } elseif ($colours[$pdbk] == 1) {
4415 $replacePairs[$searchkey] = $sk->makeKnownLinkObj(
4417 $this->mLinkHolders[
'texts'][
$key],
4418 $this->mLinkHolders[
'queries'][$key]
4420 } elseif ($colours[$pdbk] == 2) {
4421 $replacePairs[$searchkey] = $sk->makeStubLinkObj(
4423 $this->mLinkHolders[
'texts'][
$key],
4424 $this->mLinkHolders[
'queries'][$key]
4428 $replacer =
new HashtableReplacer($replacePairs, 1);
4429 wfProfileOut($fname .
'-construct');
4432 wfProfileIn($fname .
'-replace');
4433 $text = preg_replace_callback(
4434 '/(<!--LINK .*?-->)/',
4439 wfProfileOut($fname .
'-replace');
4442 # Now process interwiki link holders 4443 # This is quite a bit simpler than internal links 4444 if (!empty($this->mInterwikiLinkHolders[
'texts'])) {
4445 wfProfileIn($fname .
'-interwiki');
4446 # Make interwiki link HTML 4447 $replacePairs = array();
4448 foreach ($this->mInterwikiLinkHolders[
'texts'] as
$key => $link) {
4449 $title = $this->mInterwikiLinkHolders[
'titles'][
$key];
4450 $replacePairs[
$key] = $sk->makeLinkObj(
$title, $link);
4452 $replacer =
new HashtableReplacer($replacePairs, 1);
4454 $text = preg_replace_callback(
4455 '/<!--IWLINK (.*?)-->/',
4459 wfProfileOut($fname .
'-interwiki');
4462 wfProfileOut($fname);
4474 $fname =
'Parser::replaceLinkHoldersText';
4475 wfProfileIn($fname);
4477 $text = preg_replace_callback(
4478 '/<!--(LINK|IWLINK) (.*?)-->/',
4479 array( &$this,
'replaceLinkHoldersTextCallback' ),
4483 wfProfileOut($fname);
4494 $type = $matches[1];
4496 if (
$type ==
'LINK') {
4497 if (isset($this->mLinkHolders[
'texts'][
$key])) {
4498 return $this->mLinkHolders[
'texts'][
$key];
4500 } elseif (
$type ==
'IWLINK') {
4501 if (isset($this->mInterwikiLinkHolders[
'texts'][
$key])) {
4502 return $this->mInterwikiLinkHolders[
'texts'][
$key];
4514 $content = StringUtils::delimiterReplace(
'<nowiki>',
'</nowiki>',
'$1',
$text,
'i');
4517 return wfOpenElement(
'pre', $attribs) .
4518 Xml::escapeTagsOnly($content) .
4533 $ig =
new ImageGallery();
4534 $ig->setContextTitle($this->mTitle);
4535 $ig->setShowBytes(
false);
4536 $ig->setShowFilename(
false);
4538 $ig->useSkin($this->mOptions->getSkin());
4540 if (isset(
$params[
'caption'])) {
4541 $caption =
$params[
'caption'];
4542 $caption = htmlspecialchars($caption);
4544 $ig->setCaptionHtml($caption);
4546 if (isset(
$params[
'perrow'])) {
4547 $ig->setPerRow(
$params[
'perrow']);
4549 if (isset(
$params[
'widths'])) {
4550 $ig->setWidths(
$params[
'widths']);
4552 if (isset(
$params[
'heights'])) {
4553 $ig->setHeights(
$params[
'heights']);
4556 $lines = explode(
"\n",
$text);
4557 foreach ($lines as $line) {
4558 # match lines like these: 4559 # Image:someimage.jpg|This is some image 4561 preg_match(
"/^([^|]+)(\\|(.*))?$/", $line, $matches);
4563 if (count($matches) == 0) {
4569 # Bogus title. Ignore these so we don't bomb out later. 4572 if (isset($matches[3])) {
4573 $label = $matches[3];
4578 $pout = $this->
parse(
4585 $html = $pout->getText();
4587 $ig->add(
new Image($nt),
$html);
4589 # Only add real images (bug #5586) 4590 if ($nt->getNamespace() == NS_IMAGE) {
4591 $this->mOutput->addImage($nt->getDBkey());
4594 return $ig->toHTML();
4602 # @TODO: let the MediaHandler specify its transform parameters 4604 # Check if the options text is of the form "options|alt text" 4606 # * thumbnail make a thumbnail with enlarge-icon and caption, alignment depends on lang 4607 # * left no resizing, just left align. label is used for alt= only 4608 # * right same, but right aligned 4609 # * none same, but not aligned 4610 # * ___px scale to ___ pixels width, no aligning. e.g. use in taxobox 4611 # * center center the image 4612 # * framed Keep original image size, no magnify-button. 4613 # vertical-align values (no % or length right now): 4624 $part = array_map(
'trim', explode(
'|',
$options));
4627 $alignments = array(
'left',
'right',
'center',
'none',
'baseline',
'sub',
'super',
'top',
'text-top',
'middle',
'bottom',
'text-bottom' );
4639 $framed = $thumb =
false;
4640 $manual_thumb =
'' ;
4641 $align = $valign =
'';
4642 $sk = $this->mOptions->getSkin();
4644 foreach ($part as $val) {
4645 if (!is_null($mwThumb->matchVariableStartToEnd($val))) {
4647 } elseif (!is_null($match = $mwManualThumb->matchVariableStartToEnd($val))) {
4648 # use manually specified thumbnail 4650 $manual_thumb = $match;
4653 if (!is_null($mwAlign[$alignment]->matchVariableStartToEnd($val))) {
4654 switch ($alignment) {
4655 case 'left':
case 'right':
case 'center':
case 'none':
4656 $align = $alignment;
break;
4658 $valign = $alignment;
4663 if (!is_null($match = $mwPage->matchVariableStartToEnd($val))) {
4664 # Select a page in a multipage document 4666 } elseif (!isset(
$params[
'width']) && !is_null($match = $mwWidth->matchVariableStartToEnd($val))) {
4667 wfDebug(
"img_width match: $match\n");
4668 # $match is the image width in pixels 4670 if (preg_match(
'/^([0-9]*)x([0-9]*)$/', $match,
$m)) {
4674 $params[
'width'] = intval($match);
4676 } elseif (!is_null($mwFramed->matchVariableStartToEnd($val))) {
4683 # Strip bad stuff out of the alt text 4686 # make sure there are no placeholders in thumbnail attributes 4687 # that are later expanded to html- so expand them now and 4689 $alt = $this->mStripState->unstripBoth($alt);
4692 # Linker does the rest 4693 return $sk->makeImageLinkObj($nt, $caption, $alt, $align,
$params, $framed, $thumb, $manual_thumb, $valign);
4702 wfDebug(
"Parser output marked as uncacheable.\n");
4703 $this->mOutput->mCacheTime = -1;
4717 $text = $this->mStripState->unstripBoth(
$text);
4726 public function Title(
$x = null)
4728 return wfSetVar($this->mTitle,
$x);
4732 return wfSetVar($this->mOptions,
$x);
4736 return wfSetVar($this->mOutputType,
$x);
4745 return array_keys($this->mTagHooks);
4767 # strip NOWIKI etc. to avoid confusion (true-parameter causes HTML 4768 # comments to be stripped as well) 4773 $this->mOptions =
new ParserOptions();
4776 $striptext = $this->
strip(
$text, $stripState,
true);
4779 $this->mOptions = $oldOptions;
4781 # now that we can be sure that no pseudo-sections are in the source, 4782 # split it up by section 4783 $uniq = preg_quote($this->
uniqPrefix(),
'/');
4789 (?:$comment|<\/?noinclude>)* # Initial comments will be stripped 4790 (=+) # Should this be limited to 6? 4791 .+? # Section title... 4792 \\2 # Ending = count must match start 4793 (?:$comment|<\/?noinclude>|[ \\t]+)* # Trailing whitespace ok 4803 PREG_SPLIT_DELIM_CAPTURE
4806 if ($mode ==
"get") {
4814 } elseif ($mode ==
"replace") {
4816 $rv = $newtext .
"\n\n";
4826 $headerLine = $secs[
$index++];
4829 $headerLevel = strlen($secs[$index++]);
4833 $headerLevel = intval($secs[$index++]);
4835 $content = $secs[$index++];
4838 if ($mode ==
"get") {
4840 $rv = $headerLine . $content;
4841 $sectionLevel = $headerLevel;
4843 if ($sectionLevel && $headerLevel > $sectionLevel) {
4844 $rv .= $headerLine . $content;
4850 } elseif ($mode ==
"replace") {
4852 $rv .= $headerLine . $content;
4854 $rv .= $newtext .
"\n\n";
4855 $sectionLevel = $headerLevel;
4857 if ($headerLevel <= $sectionLevel) {
4862 $rv .= $headerLine . $content;
4867 if (is_string($rv)) {
4868 # reinsert stripped tags 4869 $rv = trim($stripState->unstripBoth($rv));
4903 if (is_null($this->mRevisionTimestamp)) {
4904 wfProfileIn(__METHOD__);
4906 $dbr = wfGetDB(DB_SLAVE);
4910 array(
'rev_id' => $this->mRevisionId ),
4926 $this->mRevisionTimestamp = $wgContLang->userAdjust(
$timestamp,
'');
4928 wfProfileOut(__METHOD__);
4940 $this->mDefaultSort = $sort;
4951 if ($this->mDefaultSort !==
false) {
4954 return $this->mTitle->getNamespace() == NS_CATEGORY
4955 ? $this->mTitle->getText()
4956 : $this->mTitle->getPrefixedText();
4969 public function replace($matches)
4971 if (substr($matches[1], -1) ==
"\n") {
4972 $this->output .= substr($matches[1], 0, -1);
4974 $this->output .= $matches[1];
4990 $this->
general =
new ReplacementArray;
4991 $this->nowiki =
new ReplacementArray;
4994 public function unstripGeneral(
$text)
4996 wfProfileIn(__METHOD__);
4998 wfProfileOut(__METHOD__);
5004 wfProfileIn(__METHOD__);
5006 wfProfileOut(__METHOD__);
5010 public function unstripBoth(
$text)
5012 wfProfileIn(__METHOD__);
5015 wfProfileOut(__METHOD__);
& doMagicLinks(&$text)
Replace special strings like "ISBN xxx" and "RFC xxx" with magic external links.
internalTidy($text)
Use the HTML tidy PECL extension to use the tidy library in-process, saving the overhead of spawning ...
validateSig($text)
Check that the user's signature contains no bad XML.
renderImageGallery($text, $params)
Renders an image gallery from a text with one line per image.
replaceExternalLinks($text)
Replace external links.
static replaceUnusualEscapes($url)
Replace unusual URL escape codes with their equivalent characters.
doHeadings($text)
Parse headers and return html.
findColonNoLinks($str, &$before, &$after)
Split up a string on ':', ignoring any occurences inside tags to prevent illegal overlapping.
initialiseVariables()
initialise the magic variables (like CURRENTMONTHNAME)
argSubstitution($matches)
Triple brace replacement – used for template arguments.
getFunctionHooks()
Get all registered function hook identifiers.
stripNoGallery(&$text)
Detect NOGALLERY magic word and set a placeholder.
replace_callback($text, $callbacks)
parse any parentheses in format ((title|part|part)) and call callbacks to get a replacement text for ...
uniqPrefix()
Accessor for mUniqPrefix.
const MW_PARSER_VERSION
Update this version number when the ParserOutput format changes in an incompatible way...
unstripNoWiki($text, $state)
Always call this after unstrip() to preserve the order.
clearState()
Clear Parser state.
formatHeadings($text, $isMain=true)
This function accomplishes several tasks: 1) Auto-number headings if that option is enabled 2) Add an...
interwikiTransclude($title, $action)
Transclude an interwiki link.
doQuotes($text)
Helper function for doAllQuotes()
if(!array_key_exists('StateId', $_REQUEST)) $id
static newFromText($text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
startExternalParse(&$title, $options, $outputType, $clearState=true)
Set up some variables which are usually set up in parse() so that an external function can call some ...
static stripAllTags($text)
Take a fragment of (potentially invalid) HTML and return a version with any tags removed, encoded as plain text.
doBlockLevels($text, $linestart)
#-
static validateTagAttributes($attribs, $element)
Take an array of attribute names and values and normalize or discard illegal values for the given ele...
static makeName($ns, $title)
maybeDoSubpageLink($target, &$text)
Handle link to subpage if necessary.
externalTidy($text)
Spawn an external HTML tidy process and get corrected markup back from it.
replaceLinkHoldersText($text)
Replace link placeholders with plain text of links (not HTML-formatted).
static createAssocArgs($args)
Clean up argument array - refactored in 1.9 so parserfunctions can use it, too.
insertStripItem($text, &$state)
Add an item to the strip state Returns the unique tag which must be inserted into the stripped text T...
const MW_COLON_STATE_CLOSETAG
braceSubstitution($piece)
Return the text of a template, after recursively replacing any variables or templates within the temp...
transformMsg($text, $options)
Transform a MediaWiki message by replacing magic variables.
static decodeCharReferences($text)
Decode any character references, numeric or named entities, in the text and return a UTF-8 string...
const MW_COLON_STATE_TAGSLASH
makeLinkHolder(&$nt, $text='', $query='', $trail='', $prefix='')
Make a link placeholder.
cleanSig($text, $parsing=false)
Clean up signature text.
const MW_COLON_STATE_TAGSTART
replaceSection($oldtext, $section, $text)
replaceInternalLinks($s)
Process [[ ]] wikilinks.
disableCache()
Set a flag in the output object indicating that the content is dynamic and shouldn't be cached...
const EXT_LINK_TEXT_CLASS
pstPass2($text, &$stripState, $user)
Pre-save transform helper function.
getRevisionTimestamp()
Get the timestamp associated with the current revision, adjusted for the default server-local timesta...
extractSections($text, $section, $mode, $newtext='')
#-
fetchScaryTemplateMaybeFromCache($url)
makeImage($nt, $options)
Parse image options text and use it to make an image.
doAllQuotes($text)
Replace single quotes with HTML markup.
static removeHTMLtags($text, $processCallback=null, $args=array())
Cleans up HTML, removes dangerous tags and attributes, and removes HTML comments. ...
if(!array_key_exists('stateid', $_REQUEST)) $state
Handle linkback() response from LinkedIn.
setHook($tag, $callback)
Create an HTML-style tag, e.g.
getUserSig(&$user)
Fetch the user's signature text, if any, and normalize to validated, ready-to-insert wikitext...
static newFromDBkey($key)
Create a new Title from a prefixed DB key.
catch(Exception $e) $message
variableSubstitution($matches)
Replace magic variables.
static http()
Fetches the global http state from ILIAS.
foreach($_POST as $key=> $value) $res
incrementIncludeSize($type, $size)
Increment an include size counter.
getSection($text, $section, $deftext='')
This function returns the text of a section, specified by a number ($section).
const MW_COLON_STATE_COMMENT
const MW_COLON_STATE_TEXT
doTableStuff($text)
parse the wiki syntax used to render tables
makeKnownLinkHolder($nt, $text='', $query='', $trail='', $prefix='')
Render a forced-blue link inline; protect against double expansion of URLs if we're in a mode that pr...
closeParagraph()
#+ Used by doBlockLevels()
static replaceUnusualEscapesCallback($matches)
Callback function used in replaceUnusualEscapes().
static cleanUrl($url, $hostname=true)
firstCallInit()
Do various kinds of initialisation on the first call of the parser.
getVariableValue($index)
Return value of a magic variable (like PAGENAME)
static newFromRedirect($text)
Create a new Title for a redirect.
armorLinks($text)
Insert a NOPARSE hacky thing into any inline links in a chunk that's going to go through further pars...
attributeStripCallback(&$text, $args)
#+ Callback from the Sanitizer for expanding items found in HTML attribute values, so they can be safely tested and escaped.
tidy($text)
Interface with html tidy, used if $wgUseTidy = true.
fetchTemplate($title)
Fetch the unparsed text of a template and register a reference to it.
maybeMakeExternalImage($url)
make an image if it's allowed, either through the global option or through the exception ...
areSubpagesAllowed()
Return true if subpage links should be expanded on this page.
if(array_key_exists('yes', $_REQUEST)) $attributes
replaceVariables($text, $args=array(), $argsOnly=false)
Replace magic variables, templates, and template arguments with the appropriate text.
static escapeId($id)
Given a value escape it so that it can be used in an id attribute and return it, this does not valida...
cleanSigInSig($text)
Strip ~~~, ~~~~ and ~~~~~ out of signatures.
foreach($mandatory_scripts as $file) $timestamp
replaceLinkHolders(&$text, $options=0)
Replace link placeholders with actual links, in the buffer Placeholders created in Skin::makeLinkObj(...
static normalizeCharReferences($text)
Ensure that any entities and character references are legal for XML and XHTML specifically.
strip($text, $state, $stripcomments=false, $dontstrip=array())
Strips and renders nowiki, pre, math, hiero If $render is set, performs necessary rendering operation...
stripToc($text)
Detect TOC magic word and set a placeholder.
replaceLinkHoldersTextCallback($matches)
setFunctionHook($id, $callback, $flags=0)
Create a function, e.g.
parse($text, &$title, $options, $linestart=true, $clearState=true, $revid=null)
Convert wikitext to HTML Do not call this function recursively.
preSaveTransform($text, &$title, $user, $options, $clearState=true)
Transform wiki markup when saving a page by doing -> conversion, substitting signatures, {{subst:}} templates, etc.
setDefaultSort($sort)
Mutator for $mDefaultSort.
static fixTagAttributes($text, $element)
Take a tag soup fragment listing an HTML element's attributes and normalize it to well-formed XML...
static legalChars()
Get a regex character class describing the legal characters in a link.
static removeHTMLcomments($text)
Remove '', and everything between.
unstrip($text, $state)
Restores pre, math, and other extensions removed by strip()
getRandomString()
Get a random string.
static & makeTitle($ns, $title)
Create a new Title from a namespace index and a DB key.
renderPreTag($text, $attribs)
Tag hook handler for 'pre'.
const EXT_IMAGE_EXTENSIONS
if(function_exists('posix_getuid') &&posix_getuid()===0) if(!array_key_exists('t', $options)) $tag
internalParse($text)
Helper function for parse() that transforms wiki markup into HTML.
const MW_COLON_STATE_COMMENTDASHDASH
for($i=6; $i< 13; $i++) for($i=1; $i< 13; $i++) $d
const MW_COLON_STATE_COMMENTDASH
replaceFreeExternalLinks($text)
Replace anything that looks like a URL with a link.
const EXT_IMAGE_FNAME_CLASS
getDefaultSort()
Accessor for $mDefaultSort Will use the title/prefixed title if none is set.
wfUrlProtocols()
Returns a regular expression of url protocols.