Exemplo n.º 1
0
 /**
  * Renders an image gallery from a text with one line per image.
  * text labels may be given by using |-style alternative text. E.g.
  *   Image:one.jpg|The number "1"
  *   Image:tree.jpg|A tree
  * given as text will return the HTML of a gallery with two images,
  * labeled 'The number "1"' and
  * 'A tree'.
  *
  * @param string $text
  * @param array $params
  * @return string HTML
  */
 function renderImageGallery($text, $params)
 {
     wfProfileIn(__METHOD__);
     $mode = false;
     if (isset($params['mode'])) {
         $mode = $params['mode'];
     }
     try {
         $ig = ImageGalleryBase::factory($mode);
     } catch (MWException $e) {
         // If invalid type set, fallback to default.
         $ig = ImageGalleryBase::factory(false);
     }
     $ig->setContextTitle($this->mTitle);
     $ig->setShowBytes(false);
     $ig->setShowFilename(false);
     $ig->setParser($this);
     $ig->setHideBadImages();
     $ig->setAttributes(Sanitizer::validateTagAttributes($params, 'table'));
     if (isset($params['showfilename'])) {
         $ig->setShowFilename(true);
     } else {
         $ig->setShowFilename(false);
     }
     if (isset($params['caption'])) {
         $caption = $params['caption'];
         $caption = htmlspecialchars($caption);
         $caption = $this->replaceInternalLinks($caption);
         $ig->setCaptionHtml($caption);
     }
     if (isset($params['perrow'])) {
         $ig->setPerRow($params['perrow']);
     }
     if (isset($params['widths'])) {
         $ig->setWidths($params['widths']);
     }
     if (isset($params['heights'])) {
         $ig->setHeights($params['heights']);
     }
     $ig->setAdditionalOptions($params);
     wfRunHooks('BeforeParserrenderImageGallery', array(&$this, &$ig));
     $lines = StringUtils::explode("\n", $text);
     foreach ($lines as $line) {
         # match lines like these:
         # Image:someimage.jpg|This is some image
         $matches = array();
         preg_match("/^([^|]+)(\\|(.*))?\$/", $line, $matches);
         # Skip empty lines
         if (count($matches) == 0) {
             continue;
         }
         if (strpos($matches[0], '%') !== false) {
             $matches[1] = rawurldecode($matches[1]);
         }
         $title = Title::newFromText($matches[1], NS_FILE);
         if (is_null($title)) {
             # Bogus title. Ignore these so we don't bomb out later.
             continue;
         }
         # We need to get what handler the file uses, to figure out parameters.
         # Note, a hook can overide the file name, and chose an entirely different
         # file (which potentially could be of a different type and have different handler).
         $options = array();
         $descQuery = false;
         wfRunHooks('BeforeParserFetchFileAndTitle', array($this, $title, &$options, &$descQuery));
         # Don't register it now, as ImageGallery does that later.
         $file = $this->fetchFileNoRegister($title, $options);
         $handler = $file ? $file->getHandler() : false;
         wfProfileIn(__METHOD__ . '-getMagicWord');
         $paramMap = array('img_alt' => 'gallery-internal-alt', 'img_link' => 'gallery-internal-link');
         if ($handler) {
             $paramMap = $paramMap + $handler->getParamMap();
             // We don't want people to specify per-image widths.
             // Additionally the width parameter would need special casing anyhow.
             unset($paramMap['img_width']);
         }
         $mwArray = new MagicWordArray(array_keys($paramMap));
         wfProfileOut(__METHOD__ . '-getMagicWord');
         $label = '';
         $alt = '';
         $link = '';
         $handlerOptions = array();
         if (isset($matches[3])) {
             // look for an |alt= definition while trying not to break existing
             // captions with multiple pipes (|) in it, until a more sensible grammar
             // is defined for images in galleries
             // FIXME: Doing recursiveTagParse at this stage, and the trim before
             // splitting on '|' is a bit odd, and different from makeImage.
             $matches[3] = $this->recursiveTagParse(trim($matches[3]));
             $parameterMatches = StringUtils::explode('|', $matches[3]);
             foreach ($parameterMatches as $parameterMatch) {
                 list($magicName, $match) = $mwArray->matchVariableStartToEnd($parameterMatch);
                 if ($magicName) {
                     $paramName = $paramMap[$magicName];
                     switch ($paramName) {
                         case 'gallery-internal-alt':
                             $alt = $this->stripAltText($match, false);
                             break;
                         case 'gallery-internal-link':
                             $linkValue = strip_tags($this->replaceLinkHoldersText($match));
                             $chars = self::EXT_LINK_URL_CLASS;
                             $prots = $this->mUrlProtocols;
                             //check to see if link matches an absolute url, if not then it must be a wiki link.
                             if (preg_match("/^({$prots}){$chars}+\$/u", $linkValue)) {
                                 $link = $linkValue;
                             } else {
                                 $localLinkTitle = Title::newFromText($linkValue);
                                 if ($localLinkTitle !== null) {
                                     $link = $localLinkTitle->getLocalURL();
                                 }
                             }
                             break;
                         default:
                             // Must be a handler specific parameter.
                             if ($handler->validateParam($paramName, $match)) {
                                 $handlerOptions[$paramName] = $match;
                             } else {
                                 // Guess not. Append it to the caption.
                                 wfDebug("{$parameterMatch} failed parameter validation\n");
                                 $label .= '|' . $parameterMatch;
                             }
                     }
                 } else {
                     // concatenate all other pipes
                     $label .= '|' . $parameterMatch;
                 }
             }
             // remove the first pipe
             $label = substr($label, 1);
         }
         $ig->add($title, $label, $alt, $link, $handlerOptions);
     }
     $html = $ig->toHTML();
     wfProfileOut(__METHOD__);
     return $html;
 }
Exemplo n.º 2
0
 /**
  * @param Parser $parser
  * @param string $text The sortkey to use
  * @param string $uarg Either "noreplace" or "noerror" (in en)
  *   both suppress errors, and noreplace does nothing if
  *   a default sortkey already exists.
  * @return string
  */
 public static function defaultsort($parser, $text, $uarg = '')
 {
     static $magicWords = null;
     if (is_null($magicWords)) {
         $magicWords = new MagicWordArray(['defaultsort_noerror', 'defaultsort_noreplace']);
     }
     $arg = $magicWords->matchStartToEnd($uarg);
     $text = trim($text);
     if (strlen($text) == 0) {
         return '';
     }
     $old = $parser->getCustomDefaultSort();
     if ($old === false || $arg !== 'defaultsort_noreplace') {
         $parser->setDefaultSort($text);
     }
     if ($old === false || $old == $text || $arg) {
         return '';
     } else {
         $converter = $parser->getConverterLanguage()->getConverter();
         return '<span class="error">' . wfMessage('duplicate-defaultsort', $converter->markNoConversion(wfEscapeWikiText($old)), $converter->markNoConversion(wfEscapeWikiText($text)))->inContentLanguage()->text() . '</span>';
     }
 }
Exemplo n.º 3
0
 /**
  * Return the text of a template, after recursively
  * replacing any variables or templates within the template.
  *
  * @param $piece Array: the parts of the template
  *  $piece['title']: the title, i.e. the part before the |
  *  $piece['parts']: the parameter array
  *  $piece['lineStart']: whether the brace was at the start of a line
  * @param $frame PPFrame The current frame, contains template arguments
  * @return String: the text of the template
  * @private
  */
 function braceSubstitution($piece, $frame)
 {
     global $wgContLang;
     wfProfileIn(__METHOD__);
     wfProfileIn(__METHOD__ . '-setup');
     # Flags
     $found = false;
     # $text has been filled
     $nowiki = false;
     # wiki markup in $text should be escaped
     $isHTML = false;
     # $text is HTML, armour it against wikitext transformation
     $forceRawInterwiki = false;
     # Force interwiki transclusion to be done in raw mode not rendered
     $isChildObj = false;
     # $text is a DOM node needing expansion in a child frame
     $isLocalObj = false;
     # $text is a DOM node needing expansion in the current frame
     # Title object, where $text came from
     $title = false;
     # $part1 is the bit before the first |, and must contain only title characters.
     # Various prefixes will be stripped from it later.
     $titleWithSpaces = $frame->expand($piece['title']);
     $part1 = trim($titleWithSpaces);
     $titleText = false;
     # Original title text preserved for various purposes
     $originalTitle = $part1;
     # $args is a list of argument nodes, starting from index 0, not including $part1
     # @todo FIXME: If piece['parts'] is null then the call to getLength() below won't work b/c this $args isn't an object
     $args = null == $piece['parts'] ? array() : $piece['parts'];
     wfProfileOut(__METHOD__ . '-setup');
     $titleProfileIn = null;
     // profile templates
     # SUBST
     wfProfileIn(__METHOD__ . '-modifiers');
     if (!$found) {
         $substMatch = $this->mSubstWords->matchStartAndRemove($part1);
         # Possibilities for substMatch: "subst", "safesubst" or FALSE
         # Decide whether to expand template or keep wikitext as-is.
         if ($this->ot['wiki']) {
             if ($substMatch === false) {
                 $literal = true;
                 # literal when in PST with no prefix
             } else {
                 $literal = false;
                 # expand when in PST with subst: or safesubst:
             }
         } else {
             if ($substMatch == 'subst') {
                 $literal = true;
                 # literal when not in PST with plain subst:
             } else {
                 $literal = false;
                 # expand when not in PST with safesubst: or no prefix
             }
         }
         if ($literal) {
             $text = $frame->virtualBracketedImplode('{{', '|', '}}', $titleWithSpaces, $args);
             $isLocalObj = true;
             $found = true;
         }
     }
     # Variables
     if (!$found && $args->getLength() == 0) {
         $id = $this->mVariables->matchStartToEnd($part1);
         if ($id !== false) {
             $text = $this->getVariableValue($id, $frame);
             if (MagicWord::getCacheTTL($id) > -1) {
                 $this->mOutput->updateCacheExpiry(MagicWord::getCacheTTL($id));
             }
             $found = true;
         }
     }
     # MSG, MSGNW and RAW
     if (!$found) {
         # Check for MSGNW:
         $mwMsgnw = MagicWord::get('msgnw');
         if ($mwMsgnw->matchStartAndRemove($part1)) {
             $nowiki = true;
         } else {
             # Remove obsolete MSG:
             $mwMsg = MagicWord::get('msg');
             $mwMsg->matchStartAndRemove($part1);
         }
         # Check for RAW:
         $mwRaw = MagicWord::get('raw');
         if ($mwRaw->matchStartAndRemove($part1)) {
             $forceRawInterwiki = true;
         }
     }
     wfProfileOut(__METHOD__ . '-modifiers');
     # Parser functions
     if (!$found) {
         wfProfileIn(__METHOD__ . '-pfunc');
         $colonPos = strpos($part1, ':');
         if ($colonPos !== false) {
             # Case sensitive functions
             $function = substr($part1, 0, $colonPos);
             if (isset($this->mFunctionSynonyms[1][$function])) {
                 $function = $this->mFunctionSynonyms[1][$function];
             } else {
                 # Case insensitive functions
                 $function = $wgContLang->lc($function);
                 if (isset($this->mFunctionSynonyms[0][$function])) {
                     $function = $this->mFunctionSynonyms[0][$function];
                 } else {
                     $function = false;
                 }
             }
             if ($function) {
                 wfProfileIn(__METHOD__ . '-pfunc-' . $function);
                 list($callback, $flags) = $this->mFunctionHooks[$function];
                 $initialArgs = array(&$this);
                 $funcArgs = array(trim(substr($part1, $colonPos + 1)));
                 if ($flags & SFH_OBJECT_ARGS) {
                     # Add a frame parameter, and pass the arguments as an array
                     $allArgs = $initialArgs;
                     $allArgs[] = $frame;
                     for ($i = 0; $i < $args->getLength(); $i++) {
                         $funcArgs[] = $args->item($i);
                     }
                     $allArgs[] = $funcArgs;
                 } else {
                     # Convert arguments to plain text
                     for ($i = 0; $i < $args->getLength(); $i++) {
                         $funcArgs[] = trim($frame->expand($args->item($i)));
                     }
                     $allArgs = array_merge($initialArgs, $funcArgs);
                 }
                 # Workaround for PHP bug 35229 and similar
                 if (!is_callable($callback)) {
                     wfProfileOut(__METHOD__ . '-pfunc-' . $function);
                     wfProfileOut(__METHOD__ . '-pfunc');
                     wfProfileOut(__METHOD__);
                     throw new MWException("Tag hook for {$function} is not callable\n");
                 }
                 $result = call_user_func_array($callback, $allArgs);
                 $found = true;
                 $noparse = true;
                 $preprocessFlags = 0;
                 if (is_array($result)) {
                     if (isset($result[0])) {
                         $text = $result[0];
                         unset($result[0]);
                     }
                     # Extract flags into the local scope
                     # This allows callers to set flags such as nowiki, found, etc.
                     extract($result);
                 } else {
                     $text = $result;
                 }
                 if (!$noparse) {
                     $text = $this->preprocessToDom($text, $preprocessFlags);
                     $isChildObj = true;
                 }
                 wfProfileOut(__METHOD__ . '-pfunc-' . $function);
             }
         }
         wfProfileOut(__METHOD__ . '-pfunc');
     }
     # Finish mangling title and then check for loops.
     # Set $title to a Title object and $titleText to the PDBK
     if (!$found) {
         $ns = NS_TEMPLATE;
         # Split the title into page and subpage
         $subpage = '';
         $part1 = $this->maybeDoSubpageLink($part1, $subpage);
         if ($subpage !== '') {
             $ns = $this->mTitle->getNamespace();
         }
         $title = Title::newFromText($part1, $ns);
         if ($title) {
             $titleText = $title->getPrefixedText();
             # Check for language variants if the template is not found
             if ($this->getConverterLanguage()->hasVariants() && $title->getArticleID() == 0) {
                 $this->getConverterLanguage()->findVariantLink($part1, $title, true);
             }
             # Do recursion depth check
             $limit = $this->mOptions->getMaxTemplateDepth();
             if ($frame->depth >= $limit) {
                 $found = true;
                 $text = '<span class="error">' . wfMessage('parser-template-recursion-depth-warning')->numParams($limit)->inContentLanguage()->text() . '</span>';
             }
         }
     }
     # Load from database
     if (!$found && $title) {
         if (!Profiler::instance()->isPersistent()) {
             # Too many unique items can kill profiling DBs/collectors
             $titleProfileIn = __METHOD__ . "-title-" . $title->getDBKey();
             wfProfileIn($titleProfileIn);
             // template in
         }
         wfProfileIn(__METHOD__ . '-loadtpl');
         if (!$title->isExternal()) {
             if ($title->isSpecialPage() && $this->mOptions->getAllowSpecialInclusion() && $this->ot['html']) {
                 // Pass the template arguments as URL parameters.
                 // "uselang" will have no effect since the Language object
                 // is forced to the one defined in ParserOptions.
                 $pageArgs = array();
                 for ($i = 0; $i < $args->getLength(); $i++) {
                     $bits = $args->item($i)->splitArg();
                     if (strval($bits['index']) === '') {
                         $name = trim($frame->expand($bits['name'], PPFrame::STRIP_COMMENTS));
                         $value = trim($frame->expand($bits['value']));
                         $pageArgs[$name] = $value;
                     }
                 }
                 // Create a new context to execute the special page
                 $context = new RequestContext();
                 $context->setTitle($title);
                 $context->setRequest(new FauxRequest($pageArgs));
                 $context->setUser($this->getUser());
                 $context->setLanguage($this->mOptions->getUserLangObj());
                 $ret = SpecialPageFactory::capturePath($title, $context);
                 if ($ret) {
                     $text = $context->getOutput()->getHTML();
                     $this->mOutput->addOutputPageMetadata($context->getOutput());
                     $found = true;
                     $isHTML = true;
                     $this->disableCache();
                 }
             } elseif (MWNamespace::isNonincludable($title->getNamespace())) {
                 $found = false;
                 # access denied
                 wfDebug(__METHOD__ . ": template inclusion denied for " . $title->getPrefixedDBkey());
             } else {
                 list($text, $title) = $this->getTemplateDom($title);
                 if ($text !== false) {
                     $found = true;
                     $isChildObj = true;
                 }
             }
             # If the title is valid but undisplayable, make a link to it
             if (!$found && ($this->ot['html'] || $this->ot['pre'])) {
                 $text = "[[:{$titleText}]]";
                 $found = true;
             }
         } elseif ($title->isTrans()) {
             # Interwiki transclusion
             if ($this->ot['html'] && !$forceRawInterwiki) {
                 $text = $this->interwikiTransclude($title, 'render');
                 $isHTML = true;
             } else {
                 $text = $this->interwikiTransclude($title, 'raw');
                 # Preprocess it like a template
                 $text = $this->preprocessToDom($text, self::PTD_FOR_INCLUSION);
                 $isChildObj = true;
             }
             $found = true;
         }
         # Do infinite loop check
         # This has to be done after redirect resolution to avoid infinite loops via redirects
         if (!$frame->loopCheck($title)) {
             $found = true;
             $text = '<span class="error">' . wfMessage('parser-template-loop-warning', $titleText)->inContentLanguage()->text() . '</span>';
             wfDebug(__METHOD__ . ": template loop broken at '{$titleText}'\n");
         }
         wfProfileOut(__METHOD__ . '-loadtpl');
     }
     # If we haven't found text to substitute by now, we're done
     # Recover the source wikitext and return it
     if (!$found) {
         $text = $frame->virtualBracketedImplode('{{', '|', '}}', $titleWithSpaces, $args);
         if ($titleProfileIn) {
             wfProfileOut($titleProfileIn);
             // template out
         }
         wfProfileOut(__METHOD__);
         return array('object' => $text);
     }
     # Expand DOM-style return values in a child frame
     if ($isChildObj) {
         # Clean up argument array
         $newFrame = $frame->newChild($args, $title);
         if ($nowiki) {
             $text = $newFrame->expand($text, PPFrame::RECOVER_ORIG);
         } elseif ($titleText !== false && $newFrame->isEmpty()) {
             # Expansion is eligible for the empty-frame cache
             if (isset($this->mTplExpandCache[$titleText])) {
                 $text = $this->mTplExpandCache[$titleText];
             } else {
                 $text = $newFrame->expand($text);
                 $this->mTplExpandCache[$titleText] = $text;
             }
         } else {
             # Uncached expansion
             $text = $newFrame->expand($text);
         }
     }
     if ($isLocalObj && $nowiki) {
         $text = $frame->expand($text, PPFrame::RECOVER_ORIG);
         $isLocalObj = false;
     }
     if ($titleProfileIn) {
         wfProfileOut($titleProfileIn);
         // template out
     }
     # Replace raw HTML by a placeholder
     if ($isHTML) {
         $text = $this->insertStripItem($text);
     } elseif ($nowiki && ($this->ot['html'] || $this->ot['pre'])) {
         # Escape nowiki-style return values
         $text = wfEscapeWikiText($text);
     } elseif (is_string($text) && !$piece['lineStart'] && preg_match('/^(?:{\\||:|;|#|\\*)/', $text)) {
         # Bug 529: if the template begins with a table or block-level
         # element, it should be treated as beginning a new line.
         # This behaviour is somewhat controversial.
         $text = "\n" . $text;
     }
     if (is_string($text) && !$this->incrementIncludeSize('post-expand', strlen($text))) {
         # Error, oversize inclusion
         if ($titleText !== false) {
             # Make a working, properly escaped link if possible (bug 23588)
             $text = "[[:{$titleText}]]";
         } else {
             # This will probably not be a working link, but at least it may
             # provide some hint of where the problem is
             preg_replace('/^:/', '', $originalTitle);
             $text = "[[:{$originalTitle}]]";
         }
         $text .= $this->insertStripItem('<!-- WARNING: template omitted, post-expand include size too large -->');
         $this->limitationWarn('post-expand-template-inclusion');
     }
     if ($isLocalObj) {
         $ret = array('object' => $text);
     } else {
         $ret = array('text' => $text);
     }
     wfProfileOut(__METHOD__);
     return $ret;
 }
 /**
  * urlencodes a string according to one of three patterns: (bug 22474)
  *
  * By default (for HTTP "query" strings), spaces are encoded as '+'.
  * Or to encode a value for the HTTP "path", spaces are encoded as '%20'.
  * For links to "wiki"s, or similar software, spaces are encoded as '_',
  *
  * @param $parser Parser object
  * @param $s String: The text to encode.
  * @param $arg String (optional): The type of encoding.
  */
 static function urlencode($parser, $s = '', $arg = null)
 {
     static $magicWords = null;
     if (is_null($magicWords)) {
         $magicWords = new MagicWordArray(array('url_path', 'url_query', 'url_wiki'));
     }
     switch ($magicWords->matchStartToEnd($arg)) {
         // Encode as though it's a wiki page, '_' for ' '.
         case 'url_wiki':
             return wfUrlencode(str_replace(' ', '_', $s));
             // Encode for an HTTP Path, '%20' for ' '.
         // Encode for an HTTP Path, '%20' for ' '.
         case 'url_path':
             return rawurlencode($s);
             // Encode for HTTP query, '+' for ' '.
         // Encode for HTTP query, '+' for ' '.
         case 'url_query':
         default:
             return urlencode($s);
     }
 }
Exemplo n.º 5
0
 /**
  * @param $parser Parser
  * @param $text String The sortkey to use
  * @param $uarg String Either "noreplace" or "noerror" (in en)
  *   both suppress errors, and noreplace does nothing if
  *   a default sortkey already exists.
  * @return string
  */
 public static function defaultsort($parser, $text, $uarg = '')
 {
     static $magicWords = null;
     if (is_null($magicWords)) {
         $magicWords = new MagicWordArray(array('defaultsort_noerror', 'defaultsort_noreplace'));
     }
     $arg = $magicWords->matchStartToEnd($uarg);
     $text = trim($text);
     if (strlen($text) == 0) {
         return '';
     }
     $old = $parser->getCustomDefaultSort();
     if ($old === false || $arg !== 'defaultsort_noreplace') {
         $parser->setDefaultSort($text);
     }
     if ($old === false || $old == $text || $arg) {
         return '';
     } else {
         return '<span class="error">' . wfMsgForContent('duplicate-defaultsort', htmlspecialchars($old), htmlspecialchars($text)) . '</span>';
     }
 }
 /**
  * urlencodes a string according to one of three patterns: (bug 22474)
  *
  * By default (for HTTP "query" strings), spaces are encoded as '+'.
  * Or to encode a value for the HTTP "path", spaces are encoded as '%20'.
  * For links to "wiki"s, or similar software, spaces are encoded as '_',
  *
  * @param $parser Parser object
  * @param $s String: The text to encode.
  * @param $arg String (optional): The type of encoding.
  */
 static function urlencode($parser, $s = '', $arg = null)
 {
     static $magicWords = null;
     if (is_null($magicWords)) {
         $magicWords = new MagicWordArray(array('url_path', 'url_query', 'url_wiki'));
     }
     switch ($magicWords->matchStartToEnd($arg)) {
         // Encode as though it's a wiki page, '_' for ' '.
         case 'url_wiki':
             $func = 'wfUrlencode';
             $s = str_replace(' ', '_', $s);
             break;
             // Encode for an HTTP Path, '%20' for ' '.
         // Encode for an HTTP Path, '%20' for ' '.
         case 'url_path':
             $func = 'rawurlencode';
             break;
             // Encode for HTTP query, '+' for ' '.
         // Encode for HTTP query, '+' for ' '.
         case 'url_query':
         default:
             $func = 'urlencode';
     }
     return $parser->markerSkipCallback($s, $func);
 }
Exemplo n.º 7
0
	/**
	 * This one parses the variable name given to optionParse to figure out
	 * whether this is a known parameter to this template.
	 *
	 * @param $value The parameter.
	 * @return The parameter's true name (for localisations purposes, etc.) as
	 *         well as its numeral found with it or false if not.
	 */
	protected function parseOptionName( $value ) {
		global $wgContLang;

		static $magicWords = null;
		if ( $magicWords === null ) {
			$magicWords = new MagicWordArray( array(
				'ta_cc_author', 'ta_cc_authorgiven',
				'ta_cc_authorsurname', 'ta_cc_authorlink',
				'ta_cc_coauthors',
				'ta_cc_editor', 'ta_cc_editorgiven',
				'ta_cc_editorsurname', 'ta_cc_editorlink',
				'ta_cc_url', 'ta_cc_title', 'ta_cc_pmc',
				'ta_cc_includedworktitle', 'ta_cc_periodical',
				'ta_cc_transitalic', 'ta_cc_transtitle',
				'ta_cc_year', 'ta_cc_publisher',
				'ta_cc_place', 'ta_cc_transtitle',
				'ta_cc_language', 'ta_cc_date',
				'ta_cc_accessdate', 'ta_cc_page',
				'ta_cc_journal', 'ta_cc_isbn',
			) );
		}

		$num = preg_replace("@.*?([0-9]+)$@is", '\1', $value);
		if ( is_numeric( $num ) )
			$name = preg_replace("@(.*?)[0-9]+$@is", '\1', $value);
		else {
			$name = $value;
			$num = null;
		}

		$name = $wgContLang->lc( $name );

		if ( $name = $magicWords->matchStartToEnd( trim($name) ) ) {
			return array(
				str_replace( 'ta_cc_', '', $name ),
				$num,
			);
		}

		# blimey, so not an option!?
		return array( false, null );
	}