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();
152 $this->mFirstCall =
true;
160 if (!$this->mFirstCall) {
164 wfProfileIn(__METHOD__);
165 global $wgAllowDisplayTitle, $wgAllowSlowParserFunctions;
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],
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) {
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>';
1311 }
else { #
$state can be
'b' or
'' 1315 } elseif (strlen($r) == 3) {
1319 } elseif (
$state ==
'bi') {
1322 } elseif (
$state ==
'ib') {
1325 } elseif (
$state ==
'both') {
1326 $output .=
'<i><b>' . $buffer .
'</b>';
1328 }
else { #
$state can be
'i' or
'' 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':
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) {
2714 $mw =&MagicWord::get($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 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 2979 $mwSubst =&MagicWord::get(
'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 2990 $mw =&MagicWord::get(
$id);
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');
3085 $mwSubst =&MagicWord::get(
'subst');
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 3101 $mwMsgnw =&MagicWord::get(
'msgnw');
3102 if ($mwMsgnw->matchStartAndRemove($part1)) {
3105 # Remove obsolete MSG: 3106 $mwMsg =&MagicWord::get(
'msg');
3107 $mwMsg->matchStartAndRemove($part1);
3111 $mwRaw =&MagicWord::get(
'raw');
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'),
3471 $time = $obj->tc_time;
3472 $text = $obj->tc_contents;
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, 3540 $mw = MagicWord::get(
'nogallery');
3541 $this->mOutput->mNoGallery = $mw->matchAndRemove(
$text) ;
3549 # if the string __NOTOC__ (not case-sensitive) occurs in the HTML, 3551 $mw = MagicWord::get(
'notoc');
3552 if ($mw->matchAndRemove(
$text)) {
3553 $this->mShowToc =
false;
3556 $mw = MagicWord::get(
'toc');
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 3596 $esw =&MagicWord::get(
'noeditsection');
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__ 3613 $mw =&MagicWord::get(
'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 3620 $mw =&MagicWord::get(
'forcetoc');
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);
3991 $substWord = MagicWord::get(
'subst');
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 4121 $mw = MagicWord::get($id);
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' );
4629 $mwAlign[$alignment] =&MagicWord::get(
'img_' . $alignment);
4631 $mwThumb =&MagicWord::get(
'img_thumbnail');
4632 $mwManualThumb =&MagicWord::get(
'img_manualthumb');
4633 $mwWidth =&MagicWord::get(
'img_width');
4634 $mwFramed =&MagicWord::get(
'img_framed');
4635 $mwPage =&MagicWord::get(
'img_page');
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.
$errors general
Prepares and displays settings form.
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='')
#-
Done rendering charts as images
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(!is_dir( $entity_dir)) exit("Fatal Error ([A-Za-z0-9]+)\+" &#(? foreach( $entity_files as $file) $output
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)
date( 'd-M-Y', $objPHPExcel->getProperties() ->getCreated())
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.
Create styles array
The data for the language used.
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'.
Add data(end) time
Method that wraps PHPs time in order to allow simulations with the workflow.
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
if(!isset($_REQUEST['ReturnTo'])) if(!isset($_REQUEST['AuthId'])) $options
getDefaultSort()
Accessor for $mDefaultSort Will use the title/prefixed title if none is set.
wfUrlProtocols()
Returns a regular expression of url protocols.