Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
95.13% covered (success)
95.13%
215 / 226
81.82% covered (warning)
81.82%
9 / 11
CRAP
0.00% covered (danger)
0.00%
0 / 1
LinkHolderArray
95.56% covered (success)
95.56%
215 / 225
81.82% covered (warning)
81.82%
9 / 11
62
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 __destruct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 merge
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 isBig
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 clear
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 makeHolder
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
2
 replace
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 replaceInternal
98.75% covered (success)
98.75%
79 / 80
0.00% covered (danger)
0.00%
0 / 1
18
 replaceInterwiki
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
3
 doVariants
89.53% covered (warning)
89.53%
77 / 86
0.00% covered (danger)
0.00%
0 / 1
28.90
 replaceText
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
2
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
24namespace MediaWiki\Parser;
25
26use MediaWiki\Cache\LinkCache;
27use MediaWiki\HookContainer\HookContainer;
28use MediaWiki\HookContainer\HookRunner;
29use MediaWiki\Language\ILanguageConverter;
30use MediaWiki\Linker\Linker;
31use MediaWiki\MainConfigNames;
32use MediaWiki\MediaWikiServices;
33use MediaWiki\Title\Title;
34use Wikimedia\HtmlArmor\HtmlArmor;
35
36/**
37 * @internal for using in Parser only.
38 *
39 * @ingroup Parser
40 */
41class LinkHolderArray {
42    /** @var array<int,array<int,array>> Indexed by numeric namespace and link ids, {@see Parser::nextLinkID} */
43    private $internals = [];
44    /** @var array<int,array> Indexed by numeric link id */
45    private $interwikis = [];
46    /** @var int */
47    private $size = 0;
48    /** @var Parser */
49    private $parent;
50    /** @var ILanguageConverter */
51    private $languageConverter;
52    /** @var HookRunner */
53    private $hookRunner;
54
55    /**
56     * @param Parser $parent
57     * @param ILanguageConverter $languageConverter
58     * @param HookContainer $hookContainer
59     */
60    public function __construct( Parser $parent, ILanguageConverter $languageConverter,
61        HookContainer $hookContainer
62    ) {
63        $this->parent = $parent;
64        $this->languageConverter = $languageConverter;
65        $this->hookRunner = new HookRunner( $hookContainer );
66    }
67
68    /**
69     * Reduce memory usage to reduce the impact of circular references
70     */
71    public function __destruct() {
72        // @phan-suppress-next-line PhanTypeSuspiciousNonTraversableForeach
73        foreach ( $this as $name => $_ ) {
74            unset( $this->$name );
75        }
76    }
77
78    /**
79     * Merge another LinkHolderArray into this one
80     * @param LinkHolderArray $other
81     */
82    public function merge( $other ) {
83        foreach ( $other->internals as $ns => $entries ) {
84            $this->size += count( $entries );
85            if ( !isset( $this->internals[$ns] ) ) {
86                $this->internals[$ns] = $entries;
87            } else {
88                $this->internals[$ns] += $entries;
89            }
90        }
91        $this->interwikis += $other->interwikis;
92    }
93
94    /**
95     * Returns true if the memory requirements of this object are getting large
96     * @return bool
97     */
98    public function isBig() {
99        $linkHolderBatchSize = MediaWikiServices::getInstance()->getMainConfig()
100            ->get( MainConfigNames::LinkHolderBatchSize );
101        return $this->size > $linkHolderBatchSize;
102    }
103
104    /**
105     * Clear all stored link holders.
106     * Make sure you don't have any text left using these link holders, before you call this
107     */
108    public function clear() {
109        $this->internals = [];
110        $this->interwikis = [];
111        $this->size = 0;
112    }
113
114    /**
115     * Make a link placeholder. The text returned can be later resolved to a real link with
116     * replaceLinkHolders(). This is done for two reasons: firstly to avoid further
117     * parsing of interwiki links, and secondly to allow all existence checks and
118     * article length checks (for stub links) to be bundled into a single query.
119     *
120     * @param Title $nt
121     * @param string $text
122     * @param string $trail [optional]
123     * @param string $prefix [optional]
124     * @return string
125     */
126    public function makeHolder( Title $nt, $text = '', $trail = '', $prefix = '' ) {
127        # Separate the link trail from the rest of the link
128        [ $inside, $trail ] = Linker::splitTrail( $trail );
129
130        $key = $this->parent->nextLinkID();
131        $entry = [
132            'title' => $nt,
133            'text' => $prefix . $text . $inside,
134            'pdbk' => $nt->getPrefixedDBkey(),
135        ];
136
137        $this->size++;
138        if ( $nt->isExternal() ) {
139            // Use a globally unique ID to keep the objects mergable
140            $this->interwikis[$key] = $entry;
141            return "<!--IWLINK'\" $key-->{$trail}";
142        } else {
143            $ns = $nt->getNamespace();
144            $this->internals[$ns][$key] = $entry;
145            return "<!--LINK'\" $ns:$key-->{$trail}";
146        }
147    }
148
149    /**
150     * Replace <!--LINK--> link placeholders with actual links, in the buffer
151     *
152     * @param string &$text
153     */
154    public function replace( &$text ) {
155        $this->replaceInternal( $text );
156        $this->replaceInterwiki( $text );
157    }
158
159    /**
160     * Replace internal links
161     * @param string &$text
162     */
163    protected function replaceInternal( &$text ) {
164        if ( !$this->internals ) {
165            return;
166        }
167
168        $classes = [];
169        $services = MediaWikiServices::getInstance();
170        $linkCache = $services->getLinkCache();
171        $output = $this->parent->getOutput();
172        $linkRenderer = $this->parent->getLinkRenderer();
173
174        $dbr = $services->getConnectionProvider()->getReplicaDatabase();
175
176        # Sort by namespace
177        ksort( $this->internals );
178
179        $pagemap = [];
180
181        # Generate query
182        $linkBatchFactory = $services->getLinkBatchFactory();
183        $lb = $linkBatchFactory->newLinkBatch();
184        $lb->setCaller( __METHOD__ );
185
186        foreach ( $this->internals as $ns => $entries ) {
187            foreach ( $entries as [ 'title' => $title, 'pdbk' => $pdbk ] ) {
188                /** @var Title $title */
189                # Check if it's a static known link, e.g. interwiki
190                if ( $title->isAlwaysKnown() ) {
191                    $classes[$pdbk] = '';
192                } elseif ( $ns === NS_SPECIAL ) {
193                    $classes[$pdbk] = 'new';
194                } else {