52 foreach ( $this as $name => $value ) {
53 unset( $this->$name );
66 foreach ( $this->internals as &$nsLinks ) {
67 foreach ( $nsLinks as &$entry ) {
68 unset( $entry[
'title'] );
74 foreach ( $this->interwikis as &$entry ) {
75 unset( $entry[
'title'] );
79 return [
'internals',
'interwikis',
'size' ];
86 foreach ( $this->internals as &$nsLinks ) {
87 foreach ( $nsLinks as &$entry ) {
94 foreach ( $this->interwikis as &$entry ) {
105 foreach ( $other->internals as $ns => $entries ) {
106 $this->size += count( $entries );
107 if ( !isset( $this->internals[$ns] ) ) {
108 $this->internals[$ns] = $entries;
110 $this->internals[$ns] += $entries;
113 $this->interwikis += $other->interwikis;
129 $this->tempIdOffset = $idOffset = $this->parent->nextLinkID();
132 # Renumber internal links
133 foreach ( $other->internals as $ns => $nsLinks ) {
134 foreach ( $nsLinks as $key => $entry ) {
135 $newKey = $idOffset + $key;
136 $this->internals[$ns][$newKey] = $entry;
137 $maxId = $newKey > $maxId ? $newKey : $maxId;
140 $texts = preg_replace_callback(
'/(<!--LINK\'" \d+:)(\d+)(-->)/',
141 [ $this,
'mergeForeignCallback' ], $texts );
143 # Renumber interwiki links
144 foreach ( $other->interwikis as $key => $entry ) {
145 $newKey = $idOffset + $key;
146 $this->interwikis[$newKey] = $entry;
147 $maxId = $newKey > $maxId ? $newKey : $maxId;
149 $texts = preg_replace_callback(
'/(<!--IWLINK\'" )(\d+)(-->)/',
150 [ $this,
'mergeForeignCallback' ], $texts );
152 # Set the parent link ID to be beyond the highest used ID
153 $this->parent->setLinkID( $maxId + 1 );
154 $this->tempIdOffset =
null;
177 while ( $pos < strlen( $text ) ) {
178 if ( !preg_match(
'/<!--LINK\'" (\d+):(\d+)-->/',
179 $text, $m, PREG_OFFSET_CAPTURE, $pos )
185 $sub->internals[$ns][$key] = $this->internals[$ns][$key];
186 $pos = $m[0][1] + strlen( $m[0][0] );
191 while ( $pos < strlen( $text ) ) {
192 if ( !preg_match(
'/<!--IWLINK\'" (\d+)-->/', $text, $m, PREG_OFFSET_CAPTURE, $pos ) ) {
196 $sub->interwikis[$key] = $this->interwikis[$key];
197 $pos = $m[0][1] + strlen( $m[0][0] );
216 $this->internals = [];
217 $this->interwikis = [];
234 public function makeHolder( $nt, $text =
'', $query = [], $trail =
'', $prefix =
'' ) {
235 if ( !is_object( $nt ) ) {
237 $retVal =
"<!-- ERROR -->{$prefix}{$text}{$trail}";
239 # Separate the link trail from the rest of the link
244 'text' => $prefix . $text . $inside,
245 'pdbk' => $nt->getPrefixedDBkey(),
247 if ( $query !== [] ) {
248 $entry[
'query'] = $query;
251 if ( $nt->isExternal() ) {
253 $key = $this->parent->nextLinkID();
254 $this->interwikis[$key] = $entry;
255 $retVal =
"<!--IWLINK'\" $key-->{$trail}";
257 $key = $this->parent->nextLinkID();
258 $ns = $nt->getNamespace();
259 $this->internals[$ns][$key] = $entry;
260 $retVal =
"<!--LINK'\" $ns:$key-->{$trail}";
283 if ( !$this->internals ) {
288 $linkCache = MediaWikiServices::getInstance()->getLinkCache();
289 $output = $this->parent->getOutput();
290 $linkRenderer = $this->parent->getLinkRenderer();
295 ksort( $this->internals );
297 $linkcolour_ids = [];
301 $lb->setCaller( __METHOD__ );
303 foreach ( $this->internals as $ns => $entries ) {
304 foreach ( $entries as $entry ) {
307 $pdbk = $entry[
'pdbk'];
309 # Skip invalid entries.
310 # Result will be ugly, but prevents crash.
311 if ( is_null(
$title ) ) {
315 # Check if it's a static known link, e.g. interwiki
316 if (
$title->isAlwaysKnown() ) {
317 $colours[$pdbk] =
'';
319 $colours[$pdbk] =
'new';
321 $id = $linkCache->getGoodLinkID( $pdbk );
323 $colours[$pdbk] = $linkRenderer->getLinkClasses(
$title );
325 $linkcolour_ids[$id] = $pdbk;
326 } elseif ( $linkCache->isBadLink( $pdbk ) ) {
327 $colours[$pdbk] =
'new';
329 # Not in the link cache, add it to the query
335 if ( !$lb->isEmpty() ) {
336 $fields = array_merge(
338 [
'page_namespace',
'page_title' ]
344 $lb->constructSet(
'page',
$dbr ),
348 # Fetch data and form into an associative array
349 # non-existent = broken
352 $pdbk =
$title->getPrefixedDBkey();
353 $linkCache->addGoodLinkObjFromRow(
$title,
$s );
355 $colours[$pdbk] = $linkRenderer->getLinkClasses(
$title );
357 $linkcolour_ids[
$s->page_id] = $pdbk;
361 if ( count( $linkcolour_ids ) ) {
363 Hooks::run(
'GetLinkColours', [ $linkcolour_ids, &$colours, $this->parent->getTitle() ] );
366 # Do a second query for different language variants of links and categories
367 if ( $this->parent->getContentLanguage()->hasVariants() ) {
371 # Construct search and replace arrays
373 foreach ( $this->internals as $ns => $entries ) {
374 foreach ( $entries as $index => $entry ) {
375 $pdbk = $entry[
'pdbk'];
377 $query = $entry[
'query'] ?? [];
379 $searchkey =
"<!--LINK'\" $key-->";
380 $displayText = $entry[
'text'];
381 if ( isset( $entry[
'selflink'] ) ) {
385 if ( $displayText ===
'' ) {
388 $displayText =
new HtmlArmor( $displayText );
390 if ( !isset( $colours[$pdbk] ) ) {
391 $colours[$pdbk] =
'new';
394 if ( $colours[$pdbk] ==
'new' ) {
395 $linkCache->addBadLinkObj(
$title );
397 $link = $linkRenderer->makeBrokenLink(
398 $title, $displayText, $attribs, $query
401 $link = $linkRenderer->makePreloadedLink(
402 $title, $displayText, $colours[$pdbk], $attribs, $query
406 $replacePairs[$searchkey] = $link;
411 $text = preg_replace_callback(
412 '/(<!--LINK\'" .*?-->)/',
413 function ( array
$matches ) use ( $replacePairs ) {
426 if ( empty( $this->interwikis ) ) {
430 # Make interwiki link HTML
431 $output = $this->parent->getOutput();
433 $linkRenderer = $this->parent->getLinkRenderer();
434 foreach ( $this->interwikis as $key => $link ) {
435 $replacePairs[$key] = $linkRenderer->makeLink(
439 $output->addInterwikiLink( $link[
'title'] );
442 $text = preg_replace_callback(
443 '/<!--IWLINK\'" (.*?)-->/',
444 function ( array
$matches ) use ( $replacePairs ) {
458 $output = $this->parent->getOutput();
459 $linkCache = MediaWikiServices::getInstance()->getLinkCache();
460 $titlesToBeConverted =
'';
466 foreach ( $this->internals as $ns => $entries ) {
470 foreach ( $entries as $index => $entry ) {
471 $pdbk = $entry[
'pdbk'];
473 if ( !isset( $colours[$pdbk] ) || $colours[$pdbk] ===
'new' ) {
474 $titlesAttrs[] = [ $index, $entry[
'title'] ];
477 $titlesToBeConverted .= $entry[
'title']->getText() .
"\0";
483 $titlesAllVariants = $this->parent->getContentLanguage()->
484 autoConvertToAllVariants( rtrim( $titlesToBeConverted,
"\0" ) );
485 $allVariantsName = array_keys( $titlesAllVariants );
486 foreach ( $titlesAllVariants as &$titlesVariant ) {
487 $titlesVariant = explode(
"\0", $titlesVariant );
491 $parentTitle = $this->parent->getTitle();
492 foreach ( $titlesAttrs as $i => $attrs ) {
494 list( $index,
$title ) = $attrs;
495 $ns =
$title->getNamespace();
496 $text =
$title->getText();
498 foreach ( $allVariantsName as $variantName ) {
499 $textVariant = $titlesAllVariants[$variantName][$i];
500 if ( $textVariant === $text ) {
509 if ( $variantTitle->equals( $parentTitle ) && !
$title->hasFragment() ) {
510 $this->internals[$ns][$index][
'selflink'] =
true;
514 $linkBatch->addObj( $variantTitle );
515 $variantMap[$variantTitle->getPrefixedDBkey()][] =
"$ns:$index";
522 foreach (
$output->getCategoryLinks() as $category ) {
524 $linkBatch->addObj( $categoryTitle );
525 $variants = $this->parent->getContentLanguage()->autoConvertToAllVariants( $category );
526 foreach ( $variants as $variant ) {
527 if ( $variant !== $category ) {
529 if ( is_null( $variantTitle ) ) {
532 $linkBatch->addObj( $variantTitle );
533 $categoryMap[$variant] = [ $category, $categoryTitle ];
538 if ( !$linkBatch->isEmpty() ) {
541 $fields = array_merge(
543 [
'page_namespace',
'page_title' ]
546 $varRes =
$dbr->select(
'page',
548 $linkBatch->constructSet(
'page',
$dbr ),
552 $linkcolour_ids = [];
553 $linkRenderer = $this->parent->getLinkRenderer();
556 foreach ( $varRes as
$s ) {
558 $varPdbk = $variantTitle->getPrefixedDBkey();
559 $vardbk = $variantTitle->getDBkey();
562 if ( isset( $variantMap[$varPdbk] ) ) {
563 $holderKeys = $variantMap[$varPdbk];
564 $linkCache->addGoodLinkObjFromRow( $variantTitle,
$s );
565 $output->addLink( $variantTitle,
$s->page_id );
569 foreach ( $holderKeys as $key ) {
570 list( $ns, $index ) = explode(
':', $key, 2 );
571 $entry =& $this->internals[$ns][$index];
572 $pdbk = $entry[
'pdbk'];
574 if ( !isset( $colours[$pdbk] ) || $colours[$pdbk] ===
'new' ) {
576 $entry[
'title'] = $variantTitle;
577 $entry[
'pdbk'] = $varPdbk;
580 $colours[$varPdbk] = $linkRenderer->getLinkClasses( $variantTitle );
581 $linkcolour_ids[
$s->page_id] = $pdbk;
586 if ( isset( $categoryMap[$vardbk] ) ) {
587 list( $oldkey, $oldtitle ) = $categoryMap[$vardbk];
588 if ( !isset( $varCategories[$oldkey] ) && !$oldtitle->exists() ) {
589 $varCategories[$oldkey] = $vardbk;
593 Hooks::run(
'GetLinkColours', [ $linkcolour_ids, &$colours, $this->parent->getTitle() ] );
596 if ( count( $varCategories ) > 0 ) {
598 $originalCats =
$output->getCategories();
599 foreach ( $originalCats as $cat => $sortkey ) {
601 if ( array_key_exists( $cat, $varCategories ) ) {
602 $newCats[$varCategories[$cat]] = $sortkey;
604 $newCats[$cat] = $sortkey;
607 $output->setCategoryLinks( $newCats );
620 $text = preg_replace_callback(
621 '/<!--(LINK|IWLINK)\'" (.*?)-->/',
622 [ $this,
'replaceTextCallback' ],
637 if (
$type ==
'LINK' ) {
638 list( $ns, $index ) = explode(
':', $key, 2 );
639 if ( isset( $this->internals[$ns][$index][
'text'] ) ) {
640 return $this->internals[$ns][$index][
'text'];
642 } elseif (
$type ==
'IWLINK' ) {
643 if ( isset( $this->interwikis[$key][
'text'] ) ) {
644 return $this->interwikis[$key][
'text'];