13define(
'MW_PARSER_VERSION',
'1.6.1');
15define(
'RLH_FOR_UPDATE', 1);
17# Allowed values for $mOutputType
21define(
'OT_PREPROCESS', 4);
23# Flags for setFunctionHook
24define(
'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
30define(
'STRIP_COMMENTS',
'HTMLCommentStrip');
32# Constants needed for external link processing
33define(
'HTTP_PROTOCOLS',
'http:\/\/|https:\/\/');
34# Everything except bracket, space, or control characters
35define(
'EXT_LINK_URL_CLASS',
'[^][<>"\\x00-\\x20\\x7F]');
36# Including space, but excluding newlines
37define(
'EXT_LINK_TEXT_CLASS',
'[^\]\\x0a\\x0d]');
38define(
'EXT_IMAGE_FNAME_CLASS',
'[A-Za-z0-9_.,~%\\-+&;#*?!=()@\\x80-\\xFF]');
39define(
'EXT_IMAGE_EXTENSIONS',
'gif|png|jpg|jpeg');
50define(
'MW_COLON_STATE_TEXT', 0);
51define(
'MW_COLON_STATE_TAG', 1);
52define(
'MW_COLON_STATE_TAGSTART', 2);
53define(
'MW_COLON_STATE_CLOSETAG', 3);
54define(
'MW_COLON_STATE_TAGSLASH', 4);
55define(
'MW_COLON_STATE_COMMENT', 5);
56define(
'MW_COLON_STATE_COMMENTDASH', 6);
57define(
'MW_COLON_STATE_COMMENTDASHDASH', 7);
104 # Cleared with clearState():
125 # These are variables reset at least once per parse regardless of $clearState
149 $this->mTagHooks = array();
150 $this->mFunctionHooks = array();
151 $this->mFunctionSynonyms = array( 0 => array(), 1 => array() );
152 $this->mFirstCall =
true;
160 if (!$this->mFirstCall) {
164 wfProfileIn(__METHOD__);
165 global $wgAllowDisplayTitle, $wgAllowSlowParserFunctions;
167 $this->
setHook(
'pre', array( $this,
'renderPreTag' ));
193 $this->
setFunctionHook(
'special', array(
'CoreParserFunctions',
'special' ));
196 if ($wgAllowDisplayTitle) {
199 if ($wgAllowSlowParserFunctions) {
204 $this->mFirstCall =
false;
205 wfProfileOut(__METHOD__);
215 wfProfileIn(__METHOD__);
216 if ($this->mFirstCall) {
219 $this->mOutput =
new ParserOutput;
220 $this->mAutonumber = 0;
221 $this->mLastSection =
'';
222 $this->mDTopen =
false;
223 $this->mIncludeCount = array();
225 $this->mArgStack = array();
226 $this->mInPre =
false;
227 $this->mInterwikiLinkHolders = array(
231 $this->mLinkHolders = array(
232 'namespaces' => array(),
234 'queries' => array(),
238 $this->mRevisionTimestamp = $this->mRevisionId =
null;
249 # Clear these on every parse, bug 4549
250 $this->mTemplates = array();
251 $this->mTemplatePath = array();
253 $this->mShowToc =
true;
254 $this->mForceTocPosition =
false;
255 $this->mIncludeSizes = array(
260 $this->mDefaultSort =
false;
262 wfRunHooks(
'ParserClearState', array( &$this ));
263 wfProfileOut(__METHOD__);
268 $this->mOutputType =
$ot;
307 global $wgUseTidy, $wgAlwaysUseTidy, $wgContLang;
308 $fname =
'Parser::parse-' . wfGetCaller();
309 wfProfileIn(__METHOD__);
320 if ($revid !==
null) {
321 $this->mRevisionId = $revid;
322 $this->mRevisionTimestamp =
null;
325 wfRunHooks(
'ParserBeforeStrip', array( &$this, &
$text, &$this->mStripState ));
327 wfRunHooks(
'ParserAfterStrip', array( &$this, &
$text, &$this->mStripState ));
329 $text = $this->mStripState->unstripGeneral(
$text);
331 # Clean up special characters, only run once, next-to-last before doBlockLevels
333 # french spaces, last one Guillemet-left
334 # only
if there is something before the space
335 '/(.) (?=\\?|:|;|!|\\302\\273)/' =>
'\\1 \\2',
336 # french spaces, Guillemet-right
337 '/(\\302\\253) /' =>
'\\1 ',
339 $text = preg_replace(array_keys($fixtags), array_values($fixtags),
$text);
346 # the position of the parserConvert() call should not be changed. it
347 # assumes that the links are all replaced and the only thing left
348 # is the <nowiki> mark.
349 # Side-effects: this calls $this->mOutput->setTitleText()
350 $text = $wgContLang->parserConvert(
$text, $this);
352 $text = $this->mStripState->unstripNoWiki(
$text);
354 wfRunHooks(
'ParserBeforeTidy', array( &$this, &
$text ));
358 if (($wgUseTidy and $this->mOptions->mTidy) or $wgAlwaysUseTidy) {
361 # attempt to sanitize at least some nesting problems
362 # (bug #2702 and quite a few others)
365 # <i>Something</i><a href="http://www.cool.com"..><i>cool></i></a>
366 '/(<([bi])>)(<([bi])>)?([^<]*)(<\/?a[^<]*>)([^<]*)(<\/\\4>)?(<\/\\2>)/' =>
367 '\\1\\3\\5\\8\\9\\6\\1\\3\\7\\8\\9',
368 # fix
up an anchor inside another anchor, only
369 # at least
for a single single nested link (bug 3695)
370 '/(<a[^>]+>)([^<]*)(<a[^>]+>[^<]*)<\/a>(.*)<\/a>/' =>
371 '\\1\\2</a>\\3</a>\\1\\4</a>',
372 # fix div inside
inline elements-
doBlockLevels won
't wrap a line which
373 # contains a div, so fix it up here; replace
374 # div with escaped text
375 '/(<([aib]) [^>]+>)([^<]*)(<div([^>]*)>)(.*)(<\/div>)([^<]*)(<\/\\2>)/
' =>
376 '\\1\\3<div\\5>\\6</div>\\8\\9
',
377 # remove empty italic or bold tag pairs, some
378 # introduced by rules above
379 '/<([bi])><\/\\1>/
' => '',
382 $text = preg_replace(
383 array_keys($tidyregs),
384 array_values($tidyregs),
389 wfRunHooks('ParserAfterTidy
', array( &$this, &$text ));
391 # Information on include size limits, for the benefit of users who try to skirt them
392 if (max($this->mIncludeSizes) > 1000) {
393 $max = $this->mOptions->getMaxIncludeSize();
395 "Pre-expand include size: {$this->mIncludeSizes['pre-
expand']} bytes\n" .
396 "Post-expand include size: {$this->mIncludeSizes['post-
expand']} bytes\n" .
397 "Template argument size: {$this->mIncludeSizes['arg
']} bytes\n" .
398 "Maximum: $max bytes\n" .
401 $this->mOutput->setText($text);
402 $this->mRevisionId = $oldRevisionId;
403 $this->mRevisionTimestamp = $oldRevisionTimestamp;
404 wfProfileOut($fname);
405 wfProfileOut(__METHOD__);
407 return $this->mOutput;
414 public function recursiveTagParse($text)
416 wfProfileIn(__METHOD__);
417 wfRunHooks('ParserBeforeStrip
', array( &$this, &$text, &$this->mStripState ));
418 $text = $this->strip($text, $this->mStripState);
419 wfRunHooks('ParserAfterStrip
', array( &$this, &$text, &$this->mStripState ));
420 $text = $this->internalParse($text);
421 wfProfileOut(__METHOD__);
429 public function preprocess($text, $title, $options)
431 wfProfileIn(__METHOD__);
433 $this->setOutputType(OT_PREPROCESS);
434 $this->mOptions = $options;
435 $this->mTitle = $title;
436 wfRunHooks('ParserBeforeStrip
', array( &$this, &$text, &$this->mStripState ));
437 $text = $this->strip($text, $this->mStripState);
438 wfRunHooks('ParserAfterStrip
', array( &$this, &$text, &$this->mStripState ));
439 if ($this->mOptions->getRemoveComments()) {
440 $text = Sanitizer::removeHTMLcomments($text);
442 $text = $this->replaceVariables($text);
443 $text = $this->mStripState->unstripBoth($text);
444 wfProfileOut(__METHOD__);
454 public function getRandomString()
456 return dechex(mt_rand(0, 0x7fffffff)) . dechex(mt_rand(0, 0x7fffffff));
459 public function &getTitle()
461 return $this->mTitle;
463 public function getOptions()
465 return $this->mOptions;
468 public function getFunctionLang()
470 global $wgLang, $wgContLang;
471 return $this->mOptions->getInterfaceMessage() ? $wgLang : $wgContLang;
492 public function extractTagsAndParams($elements, $text, &$matches, $uniq_prefix = '')
498 $taglist = implode('|
', $elements);
499 $start = "/<($taglist)(\\s+[^>]*?|\\s*?)(\/?>)|<(!--)/i";
501 while ('' != $text) {
502 $p = preg_split($start, $text, 2, PREG_SPLIT_DELIM_CAPTURE);
521 $marker = "$uniq_prefix-$element-" . sprintf('%08X
', $n++) . '-QINU
';
522 $stripped .= $marker;
524 if ($close === '/>
') {
525 // Empty element tag, <tag />
530 if ($element == '!--
') {
533 $end = "/(<\\/$element\\s*>)/i";
535 $q = preg_split($end, $inside, 2, PREG_SPLIT_DELIM_CAPTURE);
538 # No end tag -- let it run out to the end of the text.
547 $matches[$marker] = array( $element,
549 Sanitizer::decodeTagAttributes($attributes),
550 "<$element$attributes$close$content$tail" );
572 public function strip($text, $state, $stripcomments = false, $dontstrip = array())
575 wfProfileIn(__METHOD__);
576 $render = ($this->mOutputType == OT_HTML);
578 $uniq_prefix = $this->mUniqPrefix;
579 $commentState = new ReplacementArray;
580 $nowikiItems = array();
581 $generalItems = array();
583 $elements = array_merge(
584 array( 'nowiki
', 'gallery
' ),
585 array_keys($this->mTagHooks)
589 $elements[] = 'html';
591 if ($this->mOptions->getUseTeX()) {
592 $elements[] = 'math
';
595 # Removing $dontstrip tags from $elements list (currently only 'gallery
', fixing bug 2700)
596 foreach ($elements as $k => $v) {
597 if (!in_array($v, $dontstrip)) {
600 unset($elements[$k]);
604 $text = Parser::extractTagsAndParams($elements, $text, $matches, $uniq_prefix);
606 foreach ($matches as $marker => $data) {
607 list($element, $content, $params, $tag) = $data;
609 $tagName = strtolower($element);
610 wfProfileIn(__METHOD__ . "-render-$tagName");
614 if (substr($tag, -3) == '-->
') {
617 // Unclosed comment in input.
618 // Close it so later stripping can remove it
627 // Shouldn't happen otherwise. :)
630 $output = Xml::escapeTagsOnly($content);
633 $output = $wgContLang->armourMath(MathRenderer::renderMath($content));
639 if (isset($this->mTagHooks[$tagName])) {
640 $output = call_user_func_array(
641 $this->mTagHooks[$tagName],
642 array( $content,
$params, $this )
645 throw new MWException(
"Invalid call hook $element");
648 wfProfileOut(__METHOD__ .
"-render-$tagName");
657 if (!$stripcomments && $element ==
'!--') {
658 $commentState->setPair($marker,
$output);
659 } elseif ($element ==
'html' || $element ==
'nowiki') {
660 $nowikiItems[$marker] =
$output;
662 $generalItems[$marker] =
$output;
665 # Add the new items to the state
666 # We do this after the loop instead of during it to avoid slowing
667 # down the recursive unstrip
668 $state->nowiki->mergeArray($nowikiItems);
669 $state->general->mergeArray($generalItems);
671 # Unstrip comments unless explicitly told otherwise.
672 # (The comments are always stripped prior to this point, so as to
673 # not invoke any extension tags / parser hooks contained within
675 if (!$stripcomments) {
680 wfProfileOut(__METHOD__);
710 public function unstripForHTML(
$text)
712 return $this->mStripState->unstripBoth(
$text);
743 public function tidy(
$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;
767 public function externalTidy(
$text)
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 !=
'') {
819 public function internalTidy(
$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);
845 public function doTableStuff(
$text)
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);
1034 public function internalParse(
$text)
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);
1054 $text = $this->replaceVariables(
$text, $args);
1055 wfRunHooks(
'InternalParseBeforeLinks', array( &$this, &
$text, &$this->mStripState ));
1063 $text = preg_replace(
'/(^|\n)-----*/',
'\\1<hr />',
$text);
1066 $this->stripNoGallery(
$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);
1081 $text = $this->formatHeadings(
$text, $isMain);
1083 wfProfileOut($fname);
1093 public function &doMagicLinks(&
$text)
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__);
1114 public function magicLinkCallback(
$m)
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>";
1157 public function doHeadings(
$text)
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);
1178 public function doAllQuotes(
$text)
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);
1196 public function doQuotes(
$text)
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>';
1382 public function replaceExternalLinks(
$text)
1385 $fname =
'Parser::replaceExternalLinks';
1386 wfProfileIn($fname);
1388 $sk = $this->mOptions->getSkin();
1392 $s = $this->replaceFreeExternalLinks(array_shift($bits));
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
1412 $img = $this->maybeMakeExternalImage(
$text);
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:
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:
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
1445 $trail = $this->replaceFreeExternalLinks($trail);
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);
1468 public function replaceFreeExternalLinks(
$text)
1472 $fname =
'Parser::replaceFreeExternalLinks';
1473 wfProfileIn($fname);
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:
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?
1527 $text = $this->maybeMakeExternalImage(
$url);
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);
1556 public static function replaceUnusualEscapes(
$url)
1558 return preg_replace_callback(
1559 '/%[0-9A-Fa-f]{2}/',
1560 array(
'Parser',
'replaceUnusualEscapesCallback' ),
1571 private static function replaceUnusualEscapesCallback($matches)
1573 $char = urldecode($matches[0]);
1577 if ($ord > 32 && $ord < 127 && strpos(
'<>"#{}|\^~[]`;/?', $char) ===
false) {
1591 public function maybeMakeExternalImage(
$url)
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));
1612 public function replaceInternalLinks(
$s)
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());
1670 $useSubpages = $this->areSubpagesAllowed();
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:
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
1709 $text .=
']'; # so that replaceExternalLinks(
$text) works later
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
1743 $link = $this->maybeDoSubpageLink(
$m[1],
$text);
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
1838 $s .= $prefix . $this->armorLinks($this->makeImage($nt,
$text)) . $trail;
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
1855 $sortkey = $this->getDefaultSort();
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
1887 $s .= $prefix . $this->armorLinks($link) . $trail;
1888 $this->mOutput->addImage($nt->getDBkey());
1891 $s .= $this->makeKnownLinkHolder($nt,
$text,
'', $trail, $prefix);
1893 } elseif ($ns == NS_IMAGE) {
1894 $img =
new Image($nt);
1895 if (
$img->exists()) {
1899 $s .= $this->makeKnownLinkHolder($nt,
$text,
'', $trail, $prefix);
1900 $this->mOutput->addLink($nt);
1904 $s .= $this->makeLinkHolder($nt,
$text,
'', $trail, $prefix);
1906 wfProfileOut($fname);
1917 public function makeLinkHolder(&$nt,
$text =
'',
$query =
'', $trail =
'', $prefix =
'')
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__);
1959 public function makeKnownLinkHolder($nt,
$text =
'',
$query =
'', $trail =
'', $prefix =
'')
1961 list($inside, $trail) = Linker::splitTrail($trail);
1962 $sk = $this->mOptions->getSkin();
1963 $link = $sk->makeKnownLinkObj($nt,
$text,
$query, $inside, $prefix);
1964 return $this->armorLinks($link) . $trail;
1979 public function armorLinks(
$text)
1981 return preg_replace(
1983 "{$this->mUniqPrefix}NOPARSE$1",
1992 public function areSubpagesAllowed()
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);
2018 $ret =
$target; #
default return value is no change
2023 # Some namespaces don't allow subpages,
2024 # so only perform processing if subpages are allowed
2025 if ($this->areSubpagesAllowed()) {
2026 # Look at the first character
2028 # / at end means we don't want the slash to be shown
2029 $trailingSlashes = preg_match_all(
'%(/+)$%',
$target,
$m);
2030 if ($trailingSlashes) {
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);
2076 public function closeParagraph()
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.
2089 public function getCommon($st1, $st2)
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.
2107 public function openList($char)
2109 $result = $this->closeParagraph();
2113 } elseif (
'#' == $char) {
2115 } elseif (
':' == $char) {
2117 } elseif (
';' == $char) {
2119 $this->mDTopen =
true;
2127 public function nextItem($char)
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 -->';
2147 public function closeList($char)
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";
2173 public function doBlockLevels(
$text, $linestart)
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
2215 $output .= $this->nextItem(substr($pref, -1));
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.
2224 if ($this->findColonNoLinks(
$t, $term, $t2) !==
false) {
2226 $output .= $term . $this->nextItem(
':');
2229 } elseif ($prefixLength || $lastPrefixLength) {
2230 # Either open or close a level...
2231 $commonPrefixLength = $this->getCommon($pref, $lastPrefix);
2232 $paragraphStack =
false;
2234 while ($commonPrefixLength < $lastPrefixLength) {
2235 $output .= $this->closeList($lastPrefix{$lastPrefixLength-1});
2236 --$lastPrefixLength;
2238 if ($prefixLength <= $commonPrefixLength && $commonPrefixLength > 0) {
2239 $output .= $this->nextItem($pref{$commonPrefixLength-1});
2241 while ($prefixLength > $commonPrefixLength) {
2242 $char = substr($pref, $commonPrefixLength, 1);
2243 $output .= $this->openList($char);
2246 # FIXME: This is dupe of code above
2247 if ($this->findColonNoLinks(
$t, $term, $t2) !==
false) {
2249 $output .= $term . $this->nextItem(
':');
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
2269 $output .= $this->closeParagraph();
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;
2283 $output .= $this->closeParagraph() .
'<pre>';
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') {
2296 $output .= $this->closeParagraph();
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') {
2309 $output .= $this->closeParagraph() .
'<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) {
2326 $output .= $this->closeList($pref2{$prefixLength-1});
2329 if (
'' != $this->mLastSection) {
2330 $output .=
'</' . $this->mLastSection .
'>';
2331 $this->mLastSection =
'';
2334 wfProfileOut($fname);
2346 public function findColonNoLinks($str, &$before, &$after)
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);
2508 public function getVariableValue(
$index)
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();
2609 return $this->mRevisionId;
2611 return intval(substr($this->getRevisionTimestamp(), 6, 2));
2612 case 'revisionday2':
2613 return substr($this->getRevisionTimestamp(), 6, 2);
2614 case 'revisionmonth':
2615 return intval(substr($this->getRevisionTimestamp(), 4, 2));
2616 case 'revisionyear':
2617 return substr($this->getRevisionTimestamp(), 0, 4);
2618 case 'revisiontimestamp':
2619 return $this->getRevisionTimestamp();
2621 return str_replace(
'_',
' ', $wgContLang->getNsText($this->mTitle->getNamespace()));
2623 return wfUrlencode($wgContLang->getNsText($this->mTitle->getNamespace()));
2625 return $this->mTitle->canTalk() ? str_replace(
'_',
' ', $this->mTitle->getTalkNsText()) :
'';
2627 return $this->mTitle->canTalk() ? wfUrlencode($this->mTitle->getTalkNsText()) :
'';
2628 case 'subjectspace':
2629 return $this->mTitle->getSubjectNsText();
2630 case 'subjectspacee':
2631 return(wfUrlencode($this->mTitle->getSubjectNsText()));
2632 case 'currentdayname':
2633 return $varCache[
$index] = $wgContLang->getWeekdayName(
date(
'w', $ts) + 1);
2635 return $varCache[
$index] = $wgContLang->formatNum(
date(
'Y', $ts),
true);
2637 return $varCache[
$index] = $wgContLang->time(wfTimestamp(TS_MW, $ts),
false,
false);
2639 return $varCache[
$index] = $wgContLang->formatNum(
date(
'H', $ts),
true);
2643 return $varCache[
$index] = $wgContLang->formatNum((
int)
date(
'W', $ts));
2645 return $varCache[
$index] = $wgContLang->formatNum(
date(
'w', $ts));
2646 case 'localdayname':
2647 return $varCache[
$index] = $wgContLang->getWeekdayName($localDayOfWeek + 1);
2649 return $varCache[
$index] = $wgContLang->formatNum($localYear,
true);
2651 return $varCache[
$index] = $wgContLang->time($localTimestamp,
false,
false);
2653 return $varCache[
$index] = $wgContLang->formatNum($localHour,
true);
2657 return $varCache[
$index] = $wgContLang->formatNum((
int) $localWeek);
2659 return $varCache[
$index] = $wgContLang->formatNum($localDayOfWeek);
2660 case 'numberofarticles':
2661 return $varCache[
$index] = $wgContLang->formatNum(SiteStats::articles());
2662 case 'numberoffiles':
2663 return $varCache[
$index] = $wgContLang->formatNum(SiteStats::images());
2664 case 'numberofusers':
2665 return $varCache[
$index] = $wgContLang->formatNum(SiteStats::users());
2666 case 'numberofpages':
2667 return $varCache[
$index] = $wgContLang->formatNum(SiteStats::pages());
2668 case 'numberofadmins':
2669 return $varCache[
$index] = $wgContLang->formatNum(SiteStats::admins());
2670 case 'numberofedits':
2671 return $varCache[
$index] = $wgContLang->formatNum(SiteStats::edits());
2672 case 'currenttimestamp':
2673 return $varCache[
$index] = wfTimestampNow();
2674 case 'localtimestamp':
2675 return $varCache[
$index] = $localTimestamp;
2676 case 'currentversion':
2677 return $varCache[
$index] = SpecialVersion::getVersion();
2683 return $wgServerName;
2685 return $wgScriptPath;
2686 case 'directionmark':
2687 return $wgContLang->getDirMark();
2688 case 'contentlanguage':
2689 global $wgContLanguageCode;
2690 return $wgContLanguageCode;
2693 if (wfRunHooks(
'ParserGetVariableValueSwitch', array( &$this, &$varCache, &
$index, &
$ret ))) {
2706 public function initialiseVariables()
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);
2736 public function replace_callback(
$text, $callbacks)
2738 wfProfileIn(__METHOD__);
2739 $openingBraceStack = array(); #
this array will hold a stack of parentheses which are not closed yet
2740 $lastOpeningBrace = -1; # last not closed parentheses
2742 $validOpeningBraces = implode(
'', array_keys($callbacks));
2746 # Find next opening brace, closing brace or pipe
2747 if ($lastOpeningBrace == -1) {
2748 $currentClosing =
'';
2749 $search = $validOpeningBraces;
2751 $currentClosing = $openingBraceStack[$lastOpeningBrace][
'braceEnd'];
2752 $search = $validOpeningBraces .
'|' . $currentClosing;
2759 } elseif (
$text[
$i] == $currentClosing) {
2761 } elseif (isset($callbacks[
$text[
$i]])) {
2765 # Some versions of PHP have a strcspn which stops on null characters
2766 # Ignore and continue
2775 if ($found ==
'open') {
2776 # found opening brace, let's add it to parentheses stack
2777 $piece = array(
'brace' =>
$text[
$i],
2778 'braceEnd' =>
$rule[
'end'],
2782 # count opening brace characters
2783 $piece[
'count'] = strspn(
$text, $piece[
'brace'],
$i);
2784 $piece[
'startAt'] = $piece[
'partStart'] =
$i + $piece[
'count'];
2785 $i += $piece[
'count'];
2787 # we need to add to stack only if opening brace count is enough for one of the rules
2788 if ($piece[
'count'] >=
$rule[
'min']) {
2789 $lastOpeningBrace++;
2790 $openingBraceStack[$lastOpeningBrace] = $piece;
2792 } elseif ($found ==
'close') {
2793 # lets check if it is enough characters for closing brace
2794 $maxCount = $openingBraceStack[$lastOpeningBrace][
'count'];
2797 # check for maximum matching characters (if there are 5 closing
2798 # characters, we will probably need only 3 - depending on the rules)
2800 $matchingCallback =
null;
2801 $cbType = $callbacks[$openingBraceStack[$lastOpeningBrace][
'brace']];
2802 if ($count > $cbType[
'max']) {
2803 # The specified maximum exists in the callback array, unless the caller
2805 $matchingCount = $cbType[
'max'];
2807 # Count is less than the maximum
2808 # Skip any gaps in the callback array to find the true largest match
2809 # Need to use array_key_exists not isset because the callback can be null
2810 $matchingCount = $count;
2811 while ($matchingCount > 0 && !array_key_exists($matchingCount, $cbType[
'cb'])) {
2816 if ($matchingCount <= 0) {
2820 $matchingCallback = $cbType[
'cb'][$matchingCount];
2822 # let's set a title or last part (if '|' was found)
2823 if (
null === $openingBraceStack[$lastOpeningBrace][
'parts']) {
2824 $openingBraceStack[$lastOpeningBrace][
'title'] =
2827 $openingBraceStack[$lastOpeningBrace][
'partStart'],
2828 $i - $openingBraceStack[$lastOpeningBrace][
'partStart']
2831 $openingBraceStack[$lastOpeningBrace][
'parts'][] =
2834 $openingBraceStack[$lastOpeningBrace][
'partStart'],
2835 $i - $openingBraceStack[$lastOpeningBrace][
'partStart']
2839 $pieceStart = $openingBraceStack[$lastOpeningBrace][
'startAt'] - $matchingCount;
2840 $pieceEnd =
$i + $matchingCount;
2842 if (is_callable($matchingCallback)) {
2844 'text' => substr(
$text, $pieceStart, $pieceEnd - $pieceStart),
2845 'title' => trim($openingBraceStack[$lastOpeningBrace][
'title']),
2846 'parts' => $openingBraceStack[$lastOpeningBrace][
'parts'],
2847 'lineStart' => (($pieceStart > 0) && (
$text[$pieceStart-1] ==
"\n")),
2849 # finally we can call a user callback and replace piece of text
2850 $replaceWith = call_user_func($matchingCallback, $cbArgs);
2851 $text = substr(
$text, 0, $pieceStart) . $replaceWith . substr(
$text, $pieceEnd);
2852 $i = $pieceStart + strlen($replaceWith);
2854 # null value for callback means that parentheses should be parsed, but not replaced
2855 $i += $matchingCount;
2858 # reset last opening parentheses, but keep it in case there are unused characters
2859 $piece = array(
'brace' => $openingBraceStack[$lastOpeningBrace][
'brace'],
2860 'braceEnd' => $openingBraceStack[$lastOpeningBrace][
'braceEnd'],
2861 'count' => $openingBraceStack[$lastOpeningBrace][
'count'],
2864 'startAt' => $openingBraceStack[$lastOpeningBrace][
'startAt']);
2865 $openingBraceStack[$lastOpeningBrace--] =
null;
2867 if ($matchingCount < $piece[
'count']) {
2868 $piece[
'count'] -= $matchingCount;
2869 $piece[
'startAt'] -= $matchingCount;
2870 $piece[
'partStart'] = $piece[
'startAt'];
2871 # do we still qualify for any callback with remaining count?
2872 $currentCbList = $callbacks[$piece[
'brace']][
'cb'];
2873 while ($piece[
'count']) {
2874 if (array_key_exists($piece[
'count'], $currentCbList)) {
2875 $lastOpeningBrace++;
2876 $openingBraceStack[$lastOpeningBrace] = $piece;
2882 } elseif ($found ==
'pipe') {
2883 # lets set a title if it is a first separator, or next part otherwise
2884 if (
null === $openingBraceStack[$lastOpeningBrace][
'parts']) {
2885 $openingBraceStack[$lastOpeningBrace][
'title'] =
2888 $openingBraceStack[$lastOpeningBrace][
'partStart'],
2889 $i - $openingBraceStack[$lastOpeningBrace][
'partStart']
2891 $openingBraceStack[$lastOpeningBrace][
'parts'] = array();
2893 $openingBraceStack[$lastOpeningBrace][
'parts'][] =
2896 $openingBraceStack[$lastOpeningBrace][
'partStart'],
2897 $i - $openingBraceStack[$lastOpeningBrace][
'partStart']
2900 $openingBraceStack[$lastOpeningBrace][
'partStart'] = ++
$i;
2904 wfProfileOut(__METHOD__);
2923 public function replaceVariables(
$text, $args = array(), $argsOnly =
false)
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),
2958 $text = $this->replace_callback(
$text, $callbacks);
2960 array_pop($this->mArgStack);
2962 wfProfileOut($fname);
2970 public function variableSubstitution($matches)
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])) {
2992 $text = $this->getVariableValue(
$id);
2993 $this->mOutput->mContainsOldMagic =
true;
2995 $text = $matches[0];
2998 $text = $matches[0];
3000 wfProfileOut($fname);
3006 public static function createAssocArgs($args)
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;
3040 public function braceSubstitution($piece)
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']) {
3070 $replaceWith = $this->variableSubstitution(array($piece[
'text'], $piece[
'title']));
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');
3119 $lastPathLevel = $this->mTemplatePath;
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])) {
3132 # Case insensitive functions
3134 if (isset($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
3188 $part1 = $this->maybeDoSubpageLink($part1, $subpage);
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
3208 $part1 = $this->maybeDoSubpageLink($part1, $subpage);
3209 if ($subpage !==
'') {
3210 $ns = $this->mTitle->getNamespace();
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']) {
3225 if (is_string(
$text)) {
3230 $this->disableCache();
3232 } elseif ($wgNonincludableNamespaces && in_array(
$title->getNamespace(), $wgNonincludableNamespaces)) {
3234 wfDebug(
"$fname: template inclusion denied for " .
$title->getPrefixedDBkey());
3236 $articleContent = $this->fetchTemplate(
$title);
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) {
3252 $text = $this->interwikiTransclude(
$title,
'render');
3256 $text = $this->interwikiTransclude(
$title,
'raw');
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');
3277 if ($found && !$this->incrementIncludeSize(
'pre-expand', strlen(
$text))) {
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.
3317 $text = $this->strip(
$text, $this->mStripState);
3318 if ($this->ot[
'html']) {
3320 } elseif ($this->ot[
'pre'] && $this->mOptions->getRemoveComments()) {
3324 $text = $this->replaceVariables(
$text, $assocArgs);
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
3335 $text = $this->replaceVariables(
$text, $assocArgs,
true);
3338 # Prune lower levels off the recursion check path
3339 $this->mTemplatePath = $lastPathLevel;
3341 if ($found && !$this->incrementIncludeSize(
'post-expand', strlen(
$text))) {
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
3358 $text =
"\n\n" . $this->insertStripItem(
$text, $this->mStripState);
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) {
3364 $encodedname = base64_encode(
$title->getPrefixedDBkey());
3366 $encodedname = base64_encode(
"");
3369 '/(^={1,6}.*?={1,6}\s*?$)/m',
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);
3413 public function fetchTemplate(
$title)
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');
3458 return $this->fetchScaryTemplateMaybeFromCache(
$url);
3461 public function fetchScaryTemplateMaybeFromCache(
$url)
3463 global $wgTranscludeCacheExpiry;
3464 $dbr = wfGetDB(DB_SLAVE);
3465 $obj = $dbr->selectRow(
3467 array(
'tc_time',
'tc_contents'),
3468 array(
'tc_url' =>
$url)
3471 $time = $obj->tc_time;
3472 $text = $obj->tc_contents;
3473 if (
$time && time() <
$time + $wgTranscludeCacheExpiry) {
3480 return wfMsg(
'scarytranscludefailed',
$url);
3483 $dbw = wfGetDB(DB_MASTER);
3484 $dbw->replace(
'transcache', array(
'tc_url'), array(
3486 'tc_time' => time(),
3487 'tc_contents' =>
$text));
3496 public function argSubstitution($matches)
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];
3508 if (!$this->incrementIncludeSize(
'arg', strlen(
$text))) {
3509 $text = $matches[
'text'] .
3510 '<!-- WARNING: argument omitted, expansion size too large -->';
3523 public function incrementIncludeSize(
$type,
$size)
3525 if ($this->mIncludeSizes[
$type] +
$size > $this->mOptions->getMaxIncludeSize()) {
3536 public function stripNoGallery(&
$text)
3538 # if the string __NOGALLERY__ (not case-sensitive) occurs in the HTML,
3540 $mw = MagicWord::get(
'nogallery');
3541 $this->mOutput->mNoGallery = $mw->matchAndRemove(
$text) ;
3547 public function stripToc(
$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);
3584 public function formatHeadings(
$text, $isMain=
true)
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);
3845 $this->setOutputType(
OT_WIKI);
3848 $this->clearState();
3855 $text = str_replace(array_keys($pairs), array_values($pairs),
$text);
3856 $text = $this->strip(
$text, $stripState,
true, array(
'gallery' ));
3857 $text = $this->pstPass2(
$text, $stripState, $user);
3866 public function pstPass2(
$text, &$stripState, $user)
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
3890 $text = $this->strip(
$text, $stripState,
false, array(
'gallery' ));
3893 $sigText = $this->getUserSig($user);
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
3939 public function getUserSig(&$user)
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
3947 if ($this->validateSig($nickname) !==
false) {
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");
3958 $nickname = $this->cleanSigInSig($nickname);
3960 # If we're still here, make it a link to the user page
3961 $userpage = $user->getUserPage();
3962 return(
'[[' . $userpage->getPrefixedText() .
'|' . wfEscapeWikiText($nickname) .
']]');
3971 public function validateSig(
$text)
3973 return(wfIsWellFormedXmlFragment(
$text) ?
$text :
false);
3986 public function cleanSig(
$text, $parsing =
false)
3989 $this->startExternalParse($wgTitle,
new ParserOptions(), $parsing ?
OT_WIKI :
OT_MSG);
3991 $substWord = MagicWord::get(
'subst');
3992 $substRegex =
'/\{\{(?!(?:' . $substWord->getBaseRegex() .
'))/x' . $substWord->getRegexCase();
3993 $substText =
'{{' . $substWord->getSynonym(0);
3995 $text = preg_replace($substRegex, $substText,
$text);
3999 $this->clearState();
4008 public function cleanSigInSig(
$text)
4019 public function startExternalParse(&
$title,
$options, $outputType, $clearState =
true)
4023 $this->setOutputType($outputType);
4025 $this->clearState();
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;
4058 $this->setOutputType(
OT_MSG);
4059 $this->clearState();
4063 wfProfileOut($fname);
4082 public function setHook(
$tag, $callback)
4085 $oldVal = isset($this->mTagHooks[
$tag]) ? $this->mTagHooks[
$tag] :
null;
4086 $this->mTagHooks[
$tag] = $callback;
4115 public function setFunctionHook(
$id, $callback, $flags = 0)
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;
4152 public function getFunctionHooks()
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);
4472 public function replaceLinkHoldersText(
$text)
4474 $fname =
'Parser::replaceLinkHoldersText';
4475 wfProfileIn($fname);
4477 $text = preg_replace_callback(
4478 '/<!--(LINK|IWLINK) (.*?)-->/',
4479 array( &$this,
'replaceLinkHoldersTextCallback' ),
4483 wfProfileOut($fname);
4492 public function replaceLinkHoldersTextCallback($matches)
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];
4511 public function renderPreTag(
$text, $attribs)
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);
4543 $caption = $this->replaceInternalLinks($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();
4600 public function makeImage($nt,
$options)
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
4684 $alt = $this->replaceLinkHoldersText($caption);
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);
4700 public function disableCache()
4702 wfDebug(
"Parser output marked as uncacheable.\n");
4703 $this->mOutput->mCacheTime = -1;
4714 public function attributeStripCallback(&
$text, $args)
4716 $text = $this->replaceVariables(
$text, $args);
4717 $text = $this->mStripState->unstripBoth(
$text);
4726 public function Title(
$x =
null)
4728 return wfSetVar($this->mTitle,
$x);
4730 public function Options(
$x =
null)
4732 return wfSetVar($this->mOptions,
$x);
4734 public function OutputType(
$x =
null)
4736 return wfSetVar($this->mOutputType,
$x);
4743 public function getTags()
4745 return array_keys($this->mTagHooks);
4765 private function extractSections(
$text,
$section, $mode, $newtext=
'')
4767 # strip NOWIKI etc. to avoid confusion (true-parameter causes HTML
4768 # comments to be stripped as well)
4771 $oldOutputType = $this->mOutputType;
4772 $oldOptions = $this->mOptions;
4773 $this->mOptions =
new ParserOptions();
4774 $this->setOutputType(
OT_WIKI);
4776 $striptext = $this->strip(
$text, $stripState,
true);
4778 $this->setOutputType($oldOutputType);
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
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));
4889 return $this->extractSections(
$text,
$section,
"get", $deftext);
4894 return $this->extractSections($oldtext,
$section,
"replace",
$text);
4901 public function getRevisionTimestamp()
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__);
4930 return $this->mRevisionTimestamp;
4938 public function setDefaultSort($sort)
4940 $this->mDefaultSort = $sort;
4949 public function getDefaultSort()
4951 if ($this->mDefaultSort !==
false) {
4952 return $this->mDefaultSort;
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;
4996 wfProfileIn(__METHOD__);
4998 wfProfileOut(__METHOD__);
5004 wfProfileIn(__METHOD__);
5006 wfProfileOut(__METHOD__);
5012 wfProfileIn(__METHOD__);
5015 wfProfileOut(__METHOD__);
date( 'd-M-Y', $objPHPExcel->getProperties() ->getCreated())
const MW_COLON_STATE_TAGSTART
const MW_COLON_STATE_COMMENTDASHDASH
const MW_COLON_STATE_CLOSETAG
const EXT_IMAGE_FNAME_CLASS
const EXT_IMAGE_EXTENSIONS
const MW_COLON_STATE_TEXT
const EXT_LINK_TEXT_CLASS
const MW_COLON_STATE_COMMENTDASH
const MW_COLON_STATE_TAGSLASH
const MW_PARSER_VERSION
Update this version number when the ParserOutput format changes in an incompatible way,...
const MW_COLON_STATE_COMMENT
if(!isset( $_REQUEST[ 'ReturnTo'])) if(!isset($_REQUEST['AuthId'])) $options
if(!array_key_exists('stateid', $_REQUEST)) $state
Handle linkback() response from LinkedIn.
foreach($mandatory_scripts as $file) $timestamp
An exception for terminatinating execution or to throw for unit testing.
externalTidy($text)
Spawn an external HTML tidy process and get corrected markup back from it.
uniqPrefix()
Accessor for mUniqPrefix.
setFunctionHook($id, $callback, $flags=0)
Create a function, e.g.
static replaceUnusualEscapes($url)
Replace unusual URL escape codes with their equivalent characters.
setHook($tag, $callback)
Create an HTML-style tag, e.g.
strip($text, $state, $stripcomments=false, $dontstrip=array())
Strips and renders nowiki, pre, math, hiero If $render is set, performs necessary rendering operation...
internalParse($text)
Helper function for parse() that transforms wiki markup into HTML.
getRandomString()
Get a random string.
internalTidy($text)
Use the HTML tidy PECL extension to use the tidy library in-process, saving the overhead of spawning ...
tidy($text)
Interface with html tidy, used if $wgUseTidy = true.
doBlockLevels($text, $linestart)
#-
replaceLinkHolders(&$text, $options=0)
Replace link placeholders with actual links, in the buffer Placeholders created in Skin::makeLinkObj(...
renderImageGallery($text, $params)
Renders an image gallery from a text with one line per image.
firstCallInit()
Do various kinds of initialisation on the first call of the parser.
clearState()
Clear Parser state.
parse($text, &$title, $options, $linestart=true, $clearState=true, $revid=null)
Convert wikitext to HTML Do not call this function recursively.
initialiseVariables()
initialise the magic variables (like CURRENTMONTHNAME)
static removeHTMLtags($text, $processCallback=null, $args=array())
Cleans up HTML, removes dangerous tags and attributes, and removes HTML comments.
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...
static removeHTMLcomments($text)
Remove '', and everything between.
static cleanUrl($url, $hostname=true)
static decodeCharReferences($text)
Decode any character references, numeric or named entities, in the text and return a UTF-8 string.
static normalizeCharReferences($text)
Ensure that any entities and character references are legal for XML and XHTML specifically.
static stripAllTags($text)
Take a fragment of (potentially invalid) HTML and return a version with any tags removed,...
static validateTagAttributes($attribs, $element)
Take an array of attribute names and values and normalize or discard illegal values for the given ele...
static fixTagAttributes($text, $element)
Take a tag soup fragment listing an HTML element's attributes and normalize it to well-formed XML,...
static newFromRedirect($text)
Create a new Title for a redirect.
static newFromText($text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
static legalChars()
Get a regex character class describing the legal characters in a link.
static makeName($ns, $title)
static & makeTitle($ns, $title)
Create a new Title from a namespace index and a DB key.
static newFromDBkey($key)
Create a new Title from a prefixed DB key.
wfUrlProtocols()
Returns a regular expression of url protocols.
for( $i=6;$i< 13;$i++) for($i=1; $i< 13; $i++) $d
if(!array_key_exists('StateId', $_REQUEST)) $id
if(!is_dir( $entity_dir)) exit("Fatal Error ([A-Za-z0-9]+)\s+" &#(? foreach( $entity_files as $file) $output
if(function_exists( 'posix_getuid') &&posix_getuid()===0) if(!array_key_exists('t', $options)) $tag
catch(Exception $e) $message
static http()
Fetches the global http state from ILIAS.
foreach($_POST as $key=> $value) $res
echo;exit;}function LogoutNotification($SessionID){ global $ilDB;$q="SELECT session_id, data FROM usr_session WHERE expires > (\w+)\|/" PREG_SPLIT_NO_EMPTY PREG_SPLIT_DELIM_CAPTURE
$errors general
Prepares and displays settings form.