Completed
Branch master (227f0c)
by
unknown
30:54
created

LinkHolderArray::replaceInternal()   F

Complexity

Conditions 20
Paths 1281

Size

Total Lines 140
Code Lines 88

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 20
eloc 88
nc 1281
nop 1
dl 0
loc 140
rs 2
c 1
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Holder of replacement pairs for wiki links
4
 *
5
 * This program is free software; you can redistribute it and/or modify
6
 * it under the terms of the GNU General Public License as published by
7
 * the Free Software Foundation; either version 2 of the License, or
8
 * (at your option) any later version.
9
 *
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
 * GNU General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU General Public License along
16
 * with this program; if not, write to the Free Software Foundation, Inc.,
17
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18
 * http://www.gnu.org/copyleft/gpl.html
19
 *
20
 * @file
21
 * @ingroup Parser
22
 */
23
24
/**
25
 * @ingroup Parser
26
 */
27
class LinkHolderArray {
28
	public $internals = [];
29
	public $interwikis = [];
30
	public $size = 0;
31
32
	/**
33
	 * @var Parser
34
	 */
35
	public $parent;
36
	protected $tempIdOffset;
37
38
	/**
39
	 * @param Parser $parent
40
	 */
41
	public function __construct( $parent ) {
42
		$this->parent = $parent;
43
	}
44
45
	/**
46
	 * Reduce memory usage to reduce the impact of circular references
47
	 */
48
	public function __destruct() {
49
		foreach ( $this as $name => $value ) {
0 ignored issues
show
Bug introduced by
The expression $this of type this<LinkHolderArray> is not traversable.
Loading history...
50
			unset( $this->$name );
51
		}
52
	}
53
54
	/**
55
	 * Don't serialize the parent object, it is big, and not needed when it is
56
	 * a parameter to mergeForeign(), which is the only application of
57
	 * serializing at present.
58
	 *
59
	 * Compact the titles, only serialize the text form.
60
	 * @return array
61
	 */
62
	public function __sleep() {
63
		foreach ( $this->internals as &$nsLinks ) {
64
			foreach ( $nsLinks as &$entry ) {
65
				unset( $entry['title'] );
66
			}
67
		}
68
		unset( $nsLinks );
69
		unset( $entry );
70
71
		foreach ( $this->interwikis as &$entry ) {
72
			unset( $entry['title'] );
73
		}
74
		unset( $entry );
75
76
		return [ 'internals', 'interwikis', 'size' ];
77
	}
78
79
	/**
80
	 * Recreate the Title objects
81
	 */
82
	public function __wakeup() {
83
		foreach ( $this->internals as &$nsLinks ) {
84
			foreach ( $nsLinks as &$entry ) {
85
				$entry['title'] = Title::newFromText( $entry['pdbk'] );
86
			}
87
		}
88
		unset( $nsLinks );
89
		unset( $entry );
90
91
		foreach ( $this->interwikis as &$entry ) {
92
			$entry['title'] = Title::newFromText( $entry['pdbk'] );
93
		}
94
		unset( $entry );
95
	}
96
97
	/**
98
	 * Merge another LinkHolderArray into this one
99
	 * @param LinkHolderArray $other
100
	 */
101
	public function merge( $other ) {
102
		foreach ( $other->internals as $ns => $entries ) {
103
			$this->size += count( $entries );
104
			if ( !isset( $this->internals[$ns] ) ) {
105
				$this->internals[$ns] = $entries;
106
			} else {
107
				$this->internals[$ns] += $entries;
108
			}
109
		}
110
		$this->interwikis += $other->interwikis;
111
	}
112
113
	/**
114
	 * Merge a LinkHolderArray from another parser instance into this one. The
115
	 * keys will not be preserved. Any text which went with the old
116
	 * LinkHolderArray and needs to work with the new one should be passed in
117
	 * the $texts array. The strings in this array will have their link holders
118
	 * converted for use in the destination link holder. The resulting array of
119
	 * strings will be returned.
120
	 *
121
	 * @param LinkHolderArray $other
122
	 * @param array $texts Array of strings
123
	 * @return array
124
	 */
125
	public function mergeForeign( $other, $texts ) {
126
		$this->tempIdOffset = $idOffset = $this->parent->nextLinkID();
127
		$maxId = 0;
128
129
		# Renumber internal links
130
		foreach ( $other->internals as $ns => $nsLinks ) {
131 View Code Duplication
			foreach ( $nsLinks as $key => $entry ) {
132
				$newKey = $idOffset + $key;
133
				$this->internals[$ns][$newKey] = $entry;
134
				$maxId = $newKey > $maxId ? $newKey : $maxId;
135
			}
136
		}
137
		$texts = preg_replace_callback( '/(<!--LINK \d+:)(\d+)(-->)/',
138
			[ $this, 'mergeForeignCallback' ], $texts );
139
140
		# Renumber interwiki links
141 View Code Duplication
		foreach ( $other->interwikis as $key => $entry ) {
142
			$newKey = $idOffset + $key;
143
			$this->interwikis[$newKey] = $entry;
144
			$maxId = $newKey > $maxId ? $newKey : $maxId;
145
		}
146
		$texts = preg_replace_callback( '/(<!--IWLINK )(\d+)(-->)/',
147
			[ $this, 'mergeForeignCallback' ], $texts );
148
149
		# Set the parent link ID to be beyond the highest used ID
150
		$this->parent->setLinkID( $maxId + 1 );
151
		$this->tempIdOffset = null;
152
		return $texts;
153
	}
154
155
	/**
156
	 * @param array $m
157
	 * @return string
158
	 */
159
	protected function mergeForeignCallback( $m ) {
160
		return $m[1] . ( $m[2] + $this->tempIdOffset ) . $m[3];
161
	}
162
163
	/**
164
	 * Get a subset of the current LinkHolderArray which is sufficient to
165
	 * interpret the given text.
166
	 * @param string $text
167
	 * @return LinkHolderArray
168
	 */
169
	public function getSubArray( $text ) {
170
		$sub = new LinkHolderArray( $this->parent );
171
172
		# Internal links
173
		$pos = 0;
174
		while ( $pos < strlen( $text ) ) {
175
			if ( !preg_match( '/<!--LINK (\d+):(\d+)-->/',
176
				$text, $m, PREG_OFFSET_CAPTURE, $pos )
177
			) {
178
				break;
179
			}
180
			$ns = $m[1][0];
181
			$key = $m[2][0];
182
			$sub->internals[$ns][$key] = $this->internals[$ns][$key];
183
			$pos = $m[0][1] + strlen( $m[0][0] );
184
		}
185
186
		# Interwiki links
187
		$pos = 0;
188
		while ( $pos < strlen( $text ) ) {
189
			if ( !preg_match( '/<!--IWLINK (\d+)-->/', $text, $m, PREG_OFFSET_CAPTURE, $pos ) ) {
190
				break;
191
			}
192
			$key = $m[1][0];
193
			$sub->interwikis[$key] = $this->interwikis[$key];
194
			$pos = $m[0][1] + strlen( $m[0][0] );
195
		}
196
		return $sub;
197
	}
198
199
	/**
200
	 * Returns true if the memory requirements of this object are getting large
201
	 * @return bool
202
	 */
203
	public function isBig() {
204
		global $wgLinkHolderBatchSize;
205
		return $this->size > $wgLinkHolderBatchSize;
206
	}
207
208
	/**
209
	 * Clear all stored link holders.
210
	 * Make sure you don't have any text left using these link holders, before you call this
211
	 */
212
	public function clear() {
213
		$this->internals = [];
214
		$this->interwikis = [];
215
		$this->size = 0;
216
	}
217
218
	/**
219
	 * Make a link placeholder. The text returned can be later resolved to a real link with
220
	 * replaceLinkHolders(). This is done for two reasons: firstly to avoid further
221
	 * parsing of interwiki links, and secondly to allow all existence checks and
222
	 * article length checks (for stub links) to be bundled into a single query.
223
	 *
224
	 * @param Title $nt
225
	 * @param string $text
226
	 * @param array $query [optional]
227
	 * @param string $trail [optional]
228
	 * @param string $prefix [optional]
229
	 * @return string
230
	 */
231
	public function makeHolder( $nt, $text = '', $query = [], $trail = '', $prefix = '' ) {
232
		if ( !is_object( $nt ) ) {
233
			# Fail gracefully
234
			$retVal = "<!-- ERROR -->{$prefix}{$text}{$trail}";
235
		} else {
236
			# Separate the link trail from the rest of the link
237
			list( $inside, $trail ) = Linker::splitTrail( $trail );
238
239
			$entry = [
240
				'title' => $nt,
241
				'text' => $prefix . $text . $inside,
242
				'pdbk' => $nt->getPrefixedDBkey(),
243
			];
244
			if ( $query !== [] ) {
245
				$entry['query'] = $query;
246
			}
247
248
			if ( $nt->isExternal() ) {
249
				// Use a globally unique ID to keep the objects mergable
250
				$key = $this->parent->nextLinkID();
251
				$this->interwikis[$key] = $entry;
252
				$retVal = "<!--IWLINK $key-->{$trail}";
253
			} else {
254
				$key = $this->parent->nextLinkID();
255
				$ns = $nt->getNamespace();
256
				$this->internals[$ns][$key] = $entry;
257
				$retVal = "<!--LINK $ns:$key-->{$trail}";
258
			}
259
			$this->size++;
260
		}
261
		return $retVal;
262
	}
263
264
	/**
265
	 * Replace <!--LINK--> link placeholders with actual links, in the buffer
266
	 *
267
	 * @param string $text
268
	 */
269
	public function replace( &$text ) {
270
271
		$this->replaceInternal( $text );
272
		$this->replaceInterwiki( $text );
273
274
	}
275
276
	/**
277
	 * Replace internal links
278
	 * @param string $text
279
	 */
280
	protected function replaceInternal( &$text ) {
281
		if ( !$this->internals ) {
282
			return;
283
		}
284
285
		global $wgContLang;
286
287
		$colours = [];
288
		$linkCache = LinkCache::singleton();
0 ignored issues
show
Deprecated Code introduced by
The method LinkCache::singleton() has been deprecated with message: since 1.28, use MediaWikiServices instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
289
		$output = $this->parent->getOutput();
290
		$linkRenderer = $this->parent->getLinkRenderer();
291
		$threshold = $linkRenderer->getStubThreshold();
292
293
		$dbr = wfGetDB( DB_SLAVE );
294
295
		# Sort by namespace
296
		ksort( $this->internals );
297
298
		$linkcolour_ids = [];
299
300
		# Generate query
301
		$lb = new LinkBatch();
302
		$lb->setCaller( __METHOD__ );
303
304
		foreach ( $this->internals as $ns => $entries ) {
305
			foreach ( $entries as $entry ) {
306
				/** @var Title $title */
307
				$title = $entry['title'];
308
				$pdbk = $entry['pdbk'];
309
310
				# Skip invalid entries.
311
				# Result will be ugly, but prevents crash.
312
				if ( is_null( $title ) ) {
313
					continue;
314
				}
315
316
				# Check if it's a static known link, e.g. interwiki
317
				if ( $title->isAlwaysKnown() ) {
318
					$colours[$pdbk] = '';
319
				} elseif ( $ns == NS_SPECIAL ) {
320
					$colours[$pdbk] = 'new';
321
				} else {
322
					$id = $linkCache->getGoodLinkID( $pdbk );
323
					if ( $id != 0 ) {
324
						$colours[$pdbk] = Linker::getLinkColour( $title, $threshold );
325
						$output->addLink( $title, $id );
326
						$linkcolour_ids[$id] = $pdbk;
327
					} elseif ( $linkCache->isBadLink( $pdbk ) ) {
328
						$colours[$pdbk] = 'new';
329
					} else {
330
						# Not in the link cache, add it to the query
331
						$lb->addObj( $title );
332
					}
333
				}
334
			}
335
		}
336
		if ( !$lb->isEmpty() ) {
337
			$fields = array_merge(
338
				LinkCache::getSelectFields(),
339
				[ 'page_namespace', 'page_title' ]
340
			);
341
342
			$res = $dbr->select(
343
				'page',
344
				$fields,
345
				$lb->constructSet( 'page', $dbr ),
0 ignored issues
show
Bug introduced by
It seems like $lb->constructSet('page', $dbr) targeting LinkBatch::constructSet() can also be of type boolean; however, DatabaseBase::select() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
346
				__METHOD__
347
			);
348
349
			# Fetch data and form into an associative array
350
			# non-existent = broken
351
			foreach ( $res as $s ) {
0 ignored issues
show
Bug introduced by
The expression $res of type object<ResultWrapper>|boolean is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
352
				$title = Title::makeTitle( $s->page_namespace, $s->page_title );
353
				$pdbk = $title->getPrefixedDBkey();
354
				$linkCache->addGoodLinkObjFromRow( $title, $s );
355
				$output->addLink( $title, $s->page_id );
356
				$colours[$pdbk] = Linker::getLinkColour( $title, $threshold );
357
				// add id to the extension todolist
358
				$linkcolour_ids[$s->page_id] = $pdbk;
359
			}
360
			unset( $res );
361
		}
362
		if ( count( $linkcolour_ids ) ) {
363
			// pass an array of page_ids to an extension
364
			Hooks::run( 'GetLinkColours', [ $linkcolour_ids, &$colours ] );
365
		}
366
367
		# Do a second query for different language variants of links and categories
368
		if ( $wgContLang->hasVariants() ) {
369
			$this->doVariants( $colours );
370
		}
371
372
		# Construct search and replace arrays
373
		$replacePairs = [];
374
		foreach ( $this->internals as $ns => $entries ) {
375
			foreach ( $entries as $index => $entry ) {
376
				$pdbk = $entry['pdbk'];
377
				$title = $entry['title'];
378
				$query = isset( $entry['query'] ) ? $entry['query'] : [];
379
				$key = "$ns:$index";
380
				$searchkey = "<!--LINK $key-->";
381
				$displayText = $entry['text'];
382
				if ( isset( $entry['selflink'] ) ) {
383
					$replacePairs[$searchkey] = Linker::makeSelfLinkObj( $title, $displayText, $query );
384
					continue;
385
				}
386
				if ( $displayText === '' ) {
387
					$displayText = null;
388
				} else {
389
					$displayText = new HtmlArmor( $displayText );
390
				}
391
				if ( !isset( $colours[$pdbk] ) ) {
392
					$colours[$pdbk] = 'new';
393
				}
394
				$attribs = [];
395
				if ( $colours[$pdbk] == 'new' ) {
396
					$linkCache->addBadLinkObj( $title );
397
					$output->addLink( $title, 0 );
398
					$link = $linkRenderer->makeBrokenLink(
399
						$title, $displayText, $attribs, $query
400
					);
401
				} else {
402
					$link = $linkRenderer->makePreloadedLink(
403
						$title, $displayText, $colours[$pdbk], $attribs, $query
404
					);
405
				}
406
407
				$replacePairs[$searchkey] = $link;
408
			}
409
		}
410
		$replacer = new HashtableReplacer( $replacePairs, 1 );
411
412
		# Do the thing
413
		$text = preg_replace_callback(
414
			'/(<!--LINK .*?-->)/',
415
			$replacer->cb(),
416
			$text
417
		);
418
419
	}
420
421
	/**
422
	 * Replace interwiki links
423
	 * @param string $text
424
	 */
425
	protected function replaceInterwiki( &$text ) {
426
		if ( empty( $this->interwikis ) ) {
427
			return;
428
		}
429
430
		# Make interwiki link HTML
431
		$output = $this->parent->getOutput();
432
		$replacePairs = [];
433
		$linkRenderer = $this->parent->getLinkRenderer();
434
		foreach ( $this->interwikis as $key => $link ) {
435
			$replacePairs[$key] = $linkRenderer->makeLink(
436
				$link['title'],
437
				new HtmlArmor( $link['text'] )
438
			);
439
			$output->addInterwikiLink( $link['title'] );
440
		}
441
		$replacer = new HashtableReplacer( $replacePairs, 1 );
442
443
		$text = preg_replace_callback(
444
			'/<!--IWLINK (.*?)-->/',
445
			$replacer->cb(),
446
			$text );
447
	}
448
449
	/**
450
	 * Modify $this->internals and $colours according to language variant linking rules
451
	 * @param array $colours
452
	 */
453
	protected function doVariants( &$colours ) {
454
		global $wgContLang;
455
		$linkBatch = new LinkBatch();
456
		$variantMap = []; // maps $pdbkey_Variant => $keys (of link holders)
457
		$output = $this->parent->getOutput();
458
		$linkCache = LinkCache::singleton();
0 ignored issues
show
Deprecated Code introduced by
The method LinkCache::singleton() has been deprecated with message: since 1.28, use MediaWikiServices instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
459
		$threshold = $this->parent->getOptions()->getStubThreshold();
460
		$titlesToBeConverted = '';
461
		$titlesAttrs = [];
462
463
		// Concatenate titles to a single string, thus we only need auto convert the
464
		// single string to all variants. This would improve parser's performance
465
		// significantly.
466
		foreach ( $this->internals as $ns => $entries ) {
467
			if ( $ns == NS_SPECIAL ) {
468
				continue;
469
			}
470
			foreach ( $entries as $index => $entry ) {
471
				$pdbk = $entry['pdbk'];
472
				// we only deal with new links (in its first query)
473
				if ( !isset( $colours[$pdbk] ) || $colours[$pdbk] === 'new' ) {
474
					$titlesAttrs[] = [ $index, $entry['title'] ];
475
					// separate titles with \0 because it would never appears
476
					// in a valid title
477
					$titlesToBeConverted .= $entry['title']->getText() . "\0";
478
				}
479
			}
480
		}
481
482
		// Now do the conversion and explode string to text of titles
483
		$titlesAllVariants = $wgContLang->autoConvertToAllVariants( rtrim( $titlesToBeConverted, "\0" ) );
484
		$allVariantsName = array_keys( $titlesAllVariants );
485
		foreach ( $titlesAllVariants as &$titlesVariant ) {
486
			$titlesVariant = explode( "\0", $titlesVariant );
487
		}
488
489
		// Then add variants of links to link batch
490
		$parentTitle = $this->parent->getTitle();
491
		foreach ( $titlesAttrs as $i => $attrs ) {
492
			/** @var Title $title */
493
			list( $index, $title ) = $attrs;
494
			$ns = $title->getNamespace();
495
			$text = $title->getText();
496
497
			foreach ( $allVariantsName as $variantName ) {
498
				$textVariant = $titlesAllVariants[$variantName][$i];
499
				if ( $textVariant === $text ) {
500
					continue;
501
				}
502
503
				$variantTitle = Title::makeTitle( $ns, $textVariant );
504
505
				// Self-link checking for mixed/different variant titles. At this point, we
506
				// already know the exact title does not exist, so the link cannot be to a
507
				// variant of the current title that exists as a separate page.
508
				if ( $variantTitle->equals( $parentTitle ) && !$title->hasFragment() ) {
509
					$this->internals[$ns][$index]['selflink'] = true;
510
					continue 2;
511
				}
512
513
				$linkBatch->addObj( $variantTitle );
514
				$variantMap[$variantTitle->getPrefixedDBkey()][] = "$ns:$index";
515
			}
516
		}
517
518
		// process categories, check if a category exists in some variant
519
		$categoryMap = []; // maps $category_variant => $category (dbkeys)
520
		$varCategories = []; // category replacements oldDBkey => newDBkey
521
		foreach ( $output->getCategoryLinks() as $category ) {
522
			$categoryTitle = Title::makeTitleSafe( NS_CATEGORY, $category );
523
			$linkBatch->addObj( $categoryTitle );
0 ignored issues
show
Bug introduced by
It seems like $categoryTitle defined by \Title::makeTitleSafe(NS_CATEGORY, $category) on line 522 can be null; however, LinkBatch::addObj() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
524
			$variants = $wgContLang->autoConvertToAllVariants( $category );
525
			foreach ( $variants as $variant ) {
526
				if ( $variant !== $category ) {
527
					$variantTitle = Title::makeTitleSafe( NS_CATEGORY, $variant );
528
					if ( is_null( $variantTitle ) ) {
529
						continue;
530
					}
531
					$linkBatch->addObj( $variantTitle );
532
					$categoryMap[$variant] = [ $category, $categoryTitle ];
533
				}
534
			}
535
		}
536
537
		if ( !$linkBatch->isEmpty() ) {
538
			// construct query
539
			$dbr = wfGetDB( DB_SLAVE );
540
			$fields = array_merge(
541
				LinkCache::getSelectFields(),
542
				[ 'page_namespace', 'page_title' ]
543
			);
544
545
			$varRes = $dbr->select( 'page',
546
				$fields,
547
				$linkBatch->constructSet( 'page', $dbr ),
0 ignored issues
show
Bug introduced by
It seems like $linkBatch->constructSet('page', $dbr) targeting LinkBatch::constructSet() can also be of type boolean; however, DatabaseBase::select() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
548
				__METHOD__
549
			);
550
551
			$linkcolour_ids = [];
552
553
			// for each found variants, figure out link holders and replace
554
			foreach ( $varRes as $s ) {
0 ignored issues
show
Bug introduced by
The expression $varRes of type object<ResultWrapper>|boolean is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
555
				$variantTitle = Title::makeTitle( $s->page_namespace, $s->page_title );
556
				$varPdbk = $variantTitle->getPrefixedDBkey();
557
				$vardbk = $variantTitle->getDBkey();
558
559
				$holderKeys = [];
560
				if ( isset( $variantMap[$varPdbk] ) ) {
561
					$holderKeys = $variantMap[$varPdbk];
562
					$linkCache->addGoodLinkObjFromRow( $variantTitle, $s );
563
					$output->addLink( $variantTitle, $s->page_id );
564
				}
565
566
				// loop over link holders
567
				foreach ( $holderKeys as $key ) {
568
					list( $ns, $index ) = explode( ':', $key, 2 );
569
					$entry =& $this->internals[$ns][$index];
570
					$pdbk = $entry['pdbk'];
571
572
					if ( !isset( $colours[$pdbk] ) || $colours[$pdbk] === 'new' ) {
573
						// found link in some of the variants, replace the link holder data
574
						$entry['title'] = $variantTitle;
575
						$entry['pdbk'] = $varPdbk;
576
577
						// set pdbk and colour
578
						$colours[$varPdbk] = Linker::getLinkColour( $variantTitle, $threshold );
579
						$linkcolour_ids[$s->page_id] = $pdbk;
580
					}
581
				}
582
583
				// check if the object is a variant of a category
584
				if ( isset( $categoryMap[$vardbk] ) ) {
585
					list( $oldkey, $oldtitle ) = $categoryMap[$vardbk];
586
					if ( !isset( $varCategories[$oldkey] ) && !$oldtitle->exists() ) {
587
						$varCategories[$oldkey] = $vardbk;
588
					}
589
				}
590
			}
591
			Hooks::run( 'GetLinkColours', [ $linkcolour_ids, &$colours ] );
592
593
			// rebuild the categories in original order (if there are replacements)
594
			if ( count( $varCategories ) > 0 ) {
595
				$newCats = [];
596
				$originalCats = $output->getCategories();
597
				foreach ( $originalCats as $cat => $sortkey ) {
598
					// make the replacement
599
					if ( array_key_exists( $cat, $varCategories ) ) {
600
						$newCats[$varCategories[$cat]] = $sortkey;
601
					} else {
602
						$newCats[$cat] = $sortkey;
603
					}
604
				}
605
				$output->setCategoryLinks( $newCats );
606
			}
607
		}
608
	}
609
610
	/**
611
	 * Replace <!--LINK--> link placeholders with plain text of links
612
	 * (not HTML-formatted).
613
	 *
614
	 * @param string $text
615
	 * @return string
616
	 */
617
	public function replaceText( $text ) {
618
619
		$text = preg_replace_callback(
620
			'/<!--(LINK|IWLINK) (.*?)-->/',
621
			[ &$this, 'replaceTextCallback' ],
622
			$text );
623
624
		return $text;
625
	}
626
627
	/**
628
	 * Callback for replaceText()
629
	 *
630
	 * @param array $matches
631
	 * @return string
632
	 * @private
633
	 */
634
	public function replaceTextCallback( $matches ) {
635
		$type = $matches[1];
636
		$key = $matches[2];
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'];
641
			}
642
		} elseif ( $type == 'IWLINK' ) {
643
			if ( isset( $this->interwikis[$key]['text'] ) ) {
644
				return $this->interwikis[$key]['text'];
645
			}
646
		}
647
		return $matches[0];
648
	}
649
}
650