Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 116
0.00% covered (danger)
0.00%
0 / 27
CRAP
0.00% covered (danger)
0.00%
0 / 1
SiteConfig
0.00% covered (danger)
0.00%
0 / 116
0.00% covered (danger)
0.00%
0 / 27
1640
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 reset
0.00% covered (danger)
0.00%
0 / 41
0.00% covered (danger)
0.00%
0 / 1
6
 deleteNamespace
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
12
 disableSubpagesForNS
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 enableSubpagesForNS
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 updateNamespace
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
12
 setupInterwikiMap
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 interwikiMap
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 server
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 script
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 scriptpath
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 baseURI
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 allowedExternalImagePrefixes
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getMWConfigValue
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
30
 setInterwikiMagic
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 interwikiMagic
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setMagicLinkEnabled
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 magicLinkEnabled
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 fakeTimestamp
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 timezoneOffset
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 widthOption
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 registerParserTestExtension
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 unregisterParserTestExtension
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
20
 setExternalLinkTarget
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getExternalLinkTarget
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setNoFollowConfig
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 getNoFollowConfig
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2declare( strict_types = 1 );
3
4namespace Wikimedia\Parsoid\ParserTests;
5
6use Monolog\Handler\FilterHandler;
7use Monolog\Logger;
8use Psr\Log\LoggerInterface;
9use Wikimedia\Assert\Assert;
10use Wikimedia\Parsoid\Config\Api\ApiHelper;
11use Wikimedia\Parsoid\Config\Api\SiteConfig as ApiSiteConfig;
12use Wikimedia\Parsoid\Ext\ExtensionModule;
13use Wikimedia\Parsoid\Utils\ConfigUtils;
14use Wikimedia\Parsoid\Utils\Utils;
15
16class SiteConfig extends ApiSiteConfig {
17    /** @var array overrides parent-class info */
18    private $interwikiMap;
19
20    /** @var bool overrides parent-class info */
21    private $interwikiMagic;
22
23    /** @var array<string,bool> overrides parent-class info */
24    private $enabledMagicLinks = [];
25
26    /** @var array overrides parent-class server info */
27    private $serverData;
28
29    /** @var array overrides parent-class info */
30    public $allowedExternalImagePrefixes = [ '' ];
31
32    /**
33     * Init to default value for parserTests. Overrides parent-class info.
34     * @var array
35     */
36    public $responsiveReferences;
37
38    /** @var ?int */
39    public $thumbsize;
40
41    /** @var LoggerInterface */
42    public $suppressLogger;
43
44    /** If set, generate experimental Parsoid HTML v3 parser function output
45     * Individual parser tests could change this
46     */
47    public bool $v3pf;
48
49    /** @var string|false */
50    private $externalLinkTarget = false;
51
52    /** @var ?array */
53    private $noFollowConfig;
54
55    /** @inheritDoc */
56    public function __construct( ApiHelper $api, array $opts ) {
57        $logger = self::createLogger();
58        $opts['logger'] = $logger;
59        parent::__construct( $api, $opts );
60
61        // Needed for bidi-char-scrubbing html2wt tests.
62        $this->scrubBidiChars = true;
63
64        // Logger to suppress all logs but fatals (critical errors)
65        $this->suppressLogger = new Logger( "ParserTests" );
66        $errorLogHandler = $logger->getHandlers()[0];
67        $filterHandler = new FilterHandler( $errorLogHandler, Logger::CRITICAL );
68        $this->suppressLogger->pushHandler( $filterHandler );
69    }
70
71    public function reset() {
72        parent::reset();
73
74        // adjust config to match that used for PHP tests
75        // see core/tests/parser/parserTest.inc:setupGlobals() for
76        // full set of config normalizations done.
77        $this->serverData = [
78            'server'      => 'http://example.org',
79            'scriptpath'  => '/',
80            'script'      => '/index.php',
81            'articlepath' => '/wiki/$1',
82            'baseURI'     => 'http://example.org/wiki/'
83        ];
84
85        // Add 'MemoryAlpha' namespace (T53680)
86        $this->updateNamespace( [
87            'id' => 100,
88            'case' => 'first-letter',
89            'subpages' => false,
90            'canonical' => 'MemoryAlpha',
91            'name' => 'MemoryAlpha',
92        ] );
93
94        // Testing
95        if ( $this->iwp() === 'enwiki' ) {
96            $this->updateNamespace( [
97                'id' => 4,
98                'case' => 'first-letter',
99                'subpages' => true,
100                'canonical' => 'Project',
101                'name' => 'Base MW'
102            ] );
103            $this->updateNamespace( [
104                'id' => 5,
105                'case' => 'first-letter',
106                'subpages' => true,
107                'canonical' => 'Project talk',
108                'name' => 'Base MW talk'
109            ] );
110        }
111
112        // Reset other values to defaults
113        $this->responsiveReferences = [ 'enabled' => true, 'threshold' => 10 ];
114        $this->disableSubpagesForNS( 0 );
115        $this->unregisterParserTestExtension( new StyleTag() );
116        $this->unregisterParserTestExtension( new RawHTML() );
117        $this->unregisterParserTestExtension( new ParserHook() );
118        $this->unregisterParserTestExtension( new DummyAnnotation() );
119        $this->unregisterParserTestExtension( new I18nTag() );
120        $this->thumbsize = null;
121        $this->externalLinkTarget = false;
122        $this->noFollowConfig = null;
123        $this->v3pf = false;
124    }
125
126    private function deleteNamespace( string $name ): void {
127        $normName = Utils::normalizeNamespaceName( $name );
128        $id = $this->namespaceId( $normName );
129
130        if ( !$id ) {
131            $normName = $name;
132            $id = $this->namespaceId( $normName );
133        }
134
135        if ( $id ) {
136            unset( $this->nsCanon[$normName] );
137            unset( $this->nsIds[$normName] );
138            unset( $this->nsNames[$id] );
139            unset( $this->nsCase[$id] );
140            unset( $this->nsWithSubpages[$id] );
141        }
142    }
143
144    public function disableSubpagesForNS( int $ns ): void {
145        $this->nsWithSubpages[$ns] = false;
146    }
147
148    public function enableSubpagesForNS( int $ns ): void {
149        $this->nsWithSubpages[$ns] = true;
150    }
151
152    /**
153     * Update namespace info.
154     *
155     * Delete any existing namespace with the same id.
156     * Add new namespaces.
157     *
158     * @param array $ns
159     */
160    private function updateNamespace( array $ns ): void {
161        $old = $this->namespaceName( (int)$ns['id'] );
162        if ( $old ) { // Id may already be defined; if so, clear it.
163            if ( $old === Utils::normalizeNamespaceName( $ns['name'] ) ) {
164                // ParserTests does a lot redundantly.
165                return;
166            }
167            $this->deleteNamespace( $old );
168        }
169        $this->addNamespace( $ns );
170        Assert::invariant( $ns['case'] === 'first-letter',
171            'ParserTests/SiteConfig only supports first-letter case currently' );
172    }
173
174    /**
175     * Compute the interwiki map based on mock raw data.
176     * This replaces the previously computed interwiki map
177     * based on data from MockApiHelper
178     *
179     * @param array $iwData
180     */
181    public function setupInterwikiMap( array $iwData ): void {
182        $this->interwikiMap = ConfigUtils::computeInterwikiMap( $iwData );
183        $this->interwikiMapNoNamespaces = null;
184        $this->iwMatcher = null;
185    }
186
187    public function interwikiMap(): array {
188        return $this->interwikiMap;
189    }
190
191    public function server(): string {
192        return $this->serverData['server'];
193    }
194
195    public function script(): string {
196        return $this->serverData['script'];
197    }
198
199    public function scriptpath(): string {
200        return $this->serverData['scriptpath'];
201    }
202
203    public function baseURI(): string {
204        return $this->serverData['baseURI'];
205    }
206
207    public function allowedExternalImagePrefixes(): array {
208        return $this->allowedExternalImagePrefixes;
209    }
210
211    /** @inheritDoc */
212    public function getMWConfigValue( string $key ) {
213        switch ( $key ) {
214            case 'CiteResponsiveReferences':
215                return $this->responsiveReferences['enabled'];
216
217            case 'CiteResponsiveReferencesThreshold':
218                return $this->responsiveReferences['threshold'];
219
220            case 'ParsoidExperimentalParserFunctionOutput':
221                return $this->v3pf;
222
223            default:
224                return null;
225        }
226    }
227
228    public function setInterwikiMagic( bool $val ): void {
229        $this->interwikiMagic = $val;
230    }
231
232    public function interwikiMagic(): bool {
233        return $this->interwikiMagic;
234    }
235
236    /**
237     * @param string $which One of "RFC", "PMID", or "ISBN".
238     * @param bool $val
239     */
240    public function setMagicLinkEnabled( string $which, bool $val ): void {
241        $this->enabledMagicLinks[$which] = $val;
242    }
243
244    public function magicLinkEnabled( string $which ): bool {
245        // defaults to enabled
246        return $this->enabledMagicLinks[$which] ?? true;
247    }
248
249    public function fakeTimestamp(): ?int {
250        return 123;
251    }
252
253    /**
254     * Hardcode value for parser tests
255     *
256     * @return int
257     */
258    public function timezoneOffset(): int {
259        return 0;
260    }
261
262    public function widthOption(): int {
263        return $this->thumbsize ?? 180;  // wgThumbLimits setting in core ParserTestRunner
264    }
265
266    /**
267     * Register an extension for use in parser tests
268     * @param ExtensionModule $ext
269     */
270    public function registerParserTestExtension( ExtensionModule $ext ): void {
271        $this->getExtConfig(); // ensure $this->extConfig is initialized
272        $this->processExtensionModule( $ext );
273    }
274
275    /**
276     * Unregister a previously registered extension.
277     * @param ExtensionModule $ext
278     */
279    private function unregisterParserTestExtension( ExtensionModule $ext ): void {
280        $extConfig = $ext->getConfig();
281        $name = $extConfig['name'];
282
283        $this->getExtConfig(); // ensure $this->extConfig is initialized
284        foreach ( ( $extConfig['tags'] ?? [] ) as $tagConfig ) {
285            $lowerTagName = mb_strtolower( $tagConfig['name'] );
286            unset( $this->extConfig['allTags'][$lowerTagName] );
287            unset( $this->extConfig['parsoidExtTags'][$lowerTagName] );
288        }
289
290        foreach ( ( $extConfig['annotations'] ?? [] ) as $annotationTag ) {
291            $lowerTagName = mb_strtolower( $annotationTag );
292            unset( $this->extConfig['allTags'][$lowerTagName] );
293            unset( $this->extConfig['annotationTags'][$lowerTagName] );
294        }
295
296        if ( isset( $extConfig['domProcessors'] ) ) {
297            unset( $this->extConfig['domProcessors'][$name] );
298        }
299
300        /*
301         * FIXME: Unsetting contentmodels is also tricky with the current
302         * state tracked during registration. We will have to reprocess all
303         * extensions or maintain a linked list of applicable extensions
304         * for every content model
305         */
306    }
307
308    /**
309     * @param string|false $value
310     */
311    public function setExternalLinkTarget( $value ): void {
312        $this->externalLinkTarget = $value;
313    }
314
315    /**
316     * @inheritDoc
317     */
318    public function getExternalLinkTarget() {
319        return $this->externalLinkTarget;
320    }
321
322    /**
323     * @param string $key
324     * @param mixed $value
325     */
326    public function setNoFollowConfig( string $key, $value ): void {
327        $noFollowConfig = $this->getNoFollowConfig();
328        $noFollowConfig[$key] = $value;
329        $this->noFollowConfig = $noFollowConfig;
330    }
331
332    /**
333     * @inheritDoc
334     */
335    public function getNoFollowConfig(): array {
336        if ( $this->noFollowConfig === null ) {
337            $this->noFollowConfig = parent::getNoFollowConfig();
338        }
339        return $this->noFollowConfig;
340    }
341}