34 private $linkRenderer;
36 private $linkBatchFactory;
46 private $namespaceInfo;
63 private const MAX_ID_SIZE = 7;
65 private const MARKER_PREFIX =
"\x1B\"'";
89 $this->linkRenderer = $linkRenderer;
90 $this->linkBatchFactory = $linkBatchFactory;
91 $this->linkCache = $linkCache;
92 $this->repoGroup = $repoGroup;
93 $this->userLang = $userLang;
94 $this->contLang = $contLang;
95 $this->titleParser = $titleParser;
96 $this->namespaceInfo = $namespaceInfo;
97 $this->hookRunner =
new HookRunner( $hookContainer );
112 $samePage =
false, $wikiId =
false, $enableSectionLinks =
true
114 return $this->preprocessInternal( $comment,
false, $selfLinkTarget,
115 $samePage, $wikiId, $enableSectionLinks );
129 $samePage =
false, $wikiId =
false, $enableSectionLinks =
true
131 return $this->preprocessInternal( $comment,
true, $selfLinkTarget,
132 $samePage, $wikiId, $enableSectionLinks );
143 $this->flushLinkBatches();
144 return preg_replace_callback(
145 '/' . self::MARKER_PREFIX .
'([0-9]{' . self::MAX_ID_SIZE .
'})/',
147 $callback = $this->links[(int)$m[1]] ??
null;
151 return '<!-- MISSING -->';
167 private function preprocessInternal( $comment, $unsafe, $selfLinkTarget, $samePage, $wikiId,
172 $comment = strtr( $comment,
"\n\x1b",
" " );
175 $comment = \Sanitizer::escapeHtmlAllowEntities( $comment );
177 if ( $enableSectionLinks ) {
178 $comment = $this->doSectionLinks( $comment, $selfLinkTarget, $samePage, $wikiId );
180 return $this->doWikiLinks( $comment, $selfLinkTarget, $samePage, $wikiId );
199 private function doSectionLinks(
201 $selfLinkTarget =
null,
211 $comment = preg_replace_callback(
217 '!(?:(?<=(.)))?/\*\s*(.*?)\s*\*/(?:(?=(.)))?!',
218 function ( $match ) use ( &$append, $selfLinkTarget, $samePage, $wikiId ) {
220 $match += [
'',
'',
'',
'' ];
222 $pre = $match[1] !==
'';
224 $post = $match[3] !==
'';
227 $this->hookRunner->onFormatAutocomments(
228 $comment, $pre, $auto, $post,
229 Title::castFromLinkTarget( $selfLinkTarget ),
232 if ( $comment !==
null ) {
236 if ( $selfLinkTarget ) {
238 # Remove links that a user may have manually put in the autosummary
239 # This could be improved by copying as much of Parser::stripSectionName as desired.
240 $section = str_replace( [
247 if ( $section !==
'' ) {
251 $sectionTitle = $selfLinkTarget->createFragmentTarget( $section );
253 $auto = $this->makeSectionLink(
255 $this->userLang->getArrow() . $this->userLang->getDirMark() . $auto,
261 # written summary $presep autocomment (summary )
262 $pre =
wfMessage(
'autocomment-prefix' )->inContentLanguage()->escaped();
265 # autocomment $postsep written summary ( summary)
266 $auto .=
wfMessage(
'colon-separator' )->inContentLanguage()->escaped();
269 $auto =
'<span dir="auto"><span class="autocomment">' . $auto .
'</span>';
270 $append .=
'</span>';
275 return str_replace( [
'[',
']' ], [
'[',
']' ], $pre . $auto );
279 return $comment . $append;
293 private function makeSectionLink(
294 LinkTarget $target, $text, $wikiId
296 if ( $wikiId !==
null && $wikiId !==
false && !$target->isExternal() ) {
298 \WikiMap::getForeignURL(
300 $target->getNamespace() === 0
301 ? $target->getDBkey()
302 : $this->namespaceInfo->getCanonicalName( $target->getNamespace() ) .
303 ':' . $target->getDBkey(),
304 $target->getFragment()
310 return $this->linkRenderer->makePreloadedLink( $target,
new HtmlArmor( $text ),
'' );
331 private function doWikiLinks( $comment, $selfLinkTarget =
null, $samePage =
false, $wikiId =
false ) {
332 return preg_replace_callback(
335 \s*+ # ignore leading whitespace, the *+ quantifier disallows backtracking
336 :? # ignore optional leading colon
337 ([^[\]|]+) # 1. link target; page names cannot include [, ] or |
340 # Stop matching at ]] without relying on backtracking.
344 ([^[]*) # 3. link trail (the text up until the next link)
346 function ( $match ) use ( $selfLinkTarget, $samePage, $wikiId ) {
348 $medians .= preg_quote(
349 $this->namespaceInfo->getCanonicalName(
NS_MEDIA ),
'/' );
351 $medians .= preg_quote(
352 $this->contLang->getNsText(
NS_MEDIA ),
356 $comment = $match[0];
359 if ( strpos( $match[1],
'%' ) !==
false ) {
361 rawurldecode( $match[1] ),
362 [
'<' =>
'<',
'>' =>
'>' ]
367 if ( $match[2] !=
"" ) {
374 if ( preg_match(
'/^' . $medians .
'(.*)$/i', $match[1], $submatch ) ) {
376 $linkRegexp =
'/\[\[(.*?)\]\]/';
377 $linkTarget = $this->titleParser->makeTitleValueSafe(
NS_FILE, $submatch[1] );
379 $linkMarker = $this->addFileLink( $linkTarget, $text );
384 if ( isset( $match[1][0] ) && $match[1][0] ==
':' ) {
385 $match[1] = substr( $match[1], 1 );
388 if ( $match[1] !==
false && $match[1] !==
null && $match[1] !==
'' ) {
390 $this->contLang->linkTrail(),
394 $trail = $submatch[1];
398 $linkRegexp =
'/\[\[(.*?)\]\]' . preg_quote( $trail,
'/' ) .
'/';
405 $target = $this->titleParser->parseTitle( $linkTarget );
407 if ( $target->getText() ==
'' && !$target->isExternal()
408 && !$samePage && $selfLinkTarget
410 $target = $selfLinkTarget->createFragmentTarget( $target->getFragment() );
413 $linkMarker = $this->addPageLink( $target, $linkText . $inside, $wikiId );
414 $linkMarker .= $trail;
422 $comment = preg_replace(
444 private function addLinkMarker( $callback ) {
445 $nextId = count( $this->links );
446 if ( strlen( (
string)$nextId ) > self::MAX_ID_SIZE ) {
447 throw new \RuntimeException(
'Too many links in comment batch' );
449 $this->links[] = $callback;
450 return sprintf( self::MARKER_PREFIX .
"%0" . self::MAX_ID_SIZE .
'd', $nextId );
462 private function addPageLink( LinkTarget $target, $text, $wikiId ) {
463 if ( $wikiId !==
null && $wikiId !==
false && !$target->isExternal() ) {
466 \WikiMap::getForeignURL(
468 $target->getNamespace() === 0
469 ? $target->getDBkey()
470 : $this->namespaceInfo->getCanonicalName( $target->getNamespace() ) .
471 ':' . $target->getDBkey(),
472 $target->getFragment()
477 } elseif ( $this->linkCache->getGoodLinkID( $target ) ||
478 Title::newFromLinkTarget( $target )->isAlwaysKnown()
481 return $this->linkRenderer->makeKnownLink( $target,
new HtmlArmor( $text ) );
482 } elseif ( $this->linkCache->isBadLink( $target ) ) {
484 return $this->linkRenderer->makeBrokenLink( $target,
new HtmlArmor( $text ) );
488 if ( !$this->linkBatch ) {
489 $this->linkBatch = $this->linkBatchFactory->newLinkBatch();
490 $this->linkBatch->setCaller( __METHOD__ );
492 $this->linkBatch->addObj( $target );
493 return $this->addLinkMarker(
function () use ( $target, $text ) {
494 return $this->linkRenderer->makeLink( $target,
new HtmlArmor( $text ) );
505 private function addFileLink( LinkTarget $target, $html ) {
506 $this->fileBatch[] = [
509 return $this->addLinkMarker(
function () use ( $target, $html ) {
512 $this->files[$target->getDBkey()] ??
false,
521 private function flushLinkBatches() {
522 if ( $this->linkBatch ) {
523 $this->linkBatch->execute();
524 $this->linkBatch =
null;
526 if ( $this->fileBatch ) {
527 $this->files += $this->repoGroup->findFiles( $this->fileBatch );
528 $this->fileBatch = [];