MediaWiki REL1_32
ParserOutputTest.php
Go to the documentation of this file.
1<?php
2use Wikimedia\TestingAccessWrapper;
3
9
10 public static function provideIsLinkInternal() {
11 return [
12 // Different domains
13 [ false, 'http://example.org', 'http://mediawiki.org' ],
14 // Same domains
15 [ true, 'http://example.org', 'http://example.org' ],
16 [ true, 'https://example.org', 'https://example.org' ],
17 [ true, '//example.org', '//example.org' ],
18 // Same domain different cases
19 [ true, 'http://example.org', 'http://EXAMPLE.ORG' ],
20 // Paths, queries, and fragments are not relevant
21 [ true, 'http://example.org', 'http://example.org/wiki/Main_Page' ],
22 [ true, 'http://example.org', 'http://example.org?my=query' ],
23 [ true, 'http://example.org', 'http://example.org#its-a-fragment' ],
24 // Different protocols
25 [ false, 'http://example.org', 'https://example.org' ],
26 [ false, 'https://example.org', 'http://example.org' ],
27 // Protocol relative servers always match http and https links
28 [ true, '//example.org', 'http://example.org' ],
29 [ true, '//example.org', 'https://example.org' ],
30 // But they don't match strange things like this
31 [ false, '//example.org', 'irc://example.org' ],
32 ];
33 }
34
35 public function tearDown() {
36 MWTimestamp::setFakeTime( false );
37
38 parent::tearDown();
39 }
40
46 public function testIsLinkInternal( $shouldMatch, $server, $url ) {
47 $this->assertEquals( $shouldMatch, ParserOutput::isLinkInternal( $server, $url ) );
48 }
49
54 public function testExtensionData() {
55 $po = new ParserOutput();
56
57 $po->setExtensionData( "one", "Foo" );
58
59 $this->assertEquals( "Foo", $po->getExtensionData( "one" ) );
60 $this->assertNull( $po->getExtensionData( "spam" ) );
61
62 $po->setExtensionData( "two", "Bar" );
63 $this->assertEquals( "Foo", $po->getExtensionData( "one" ) );
64 $this->assertEquals( "Bar", $po->getExtensionData( "two" ) );
65
66 $po->setExtensionData( "one", null );
67 $this->assertNull( $po->getExtensionData( "one" ) );
68 $this->assertEquals( "Bar", $po->getExtensionData( "two" ) );
69 }
70
77 public function testProperties() {
78 $po = new ParserOutput();
79
80 $po->setProperty( 'foo', 'val' );
81
82 $properties = $po->getProperties();
83 $this->assertEquals( $po->getProperty( 'foo' ), 'val' );
84 $this->assertEquals( $properties['foo'], 'val' );
85
86 $po->setProperty( 'foo', 'second val' );
87
88 $properties = $po->getProperties();
89 $this->assertEquals( $po->getProperty( 'foo' ), 'second val' );
90 $this->assertEquals( $properties['foo'], 'second val' );
91
92 $po->unsetProperty( 'foo' );
93
94 $properties = $po->getProperties();
95 $this->assertEquals( $po->getProperty( 'foo' ), false );
96 $this->assertArrayNotHasKey( 'foo', $properties );
97 }
98
105 public function testWrapperDivClass() {
106 $po = new ParserOutput();
107
108 $po->setText( 'Kittens' );
109 $this->assertContains( 'Kittens', $po->getText() );
110 $this->assertNotContains( '<div', $po->getText() );
111 $this->assertSame( 'Kittens', $po->getRawText() );
112
113 $po->addWrapperDivClass( 'foo' );
114 $text = $po->getText();
115 $this->assertContains( 'Kittens', $text );
116 $this->assertContains( '<div', $text );
117 $this->assertContains( 'class="foo"', $text );
118
119 $po->addWrapperDivClass( 'bar' );
120 $text = $po->getText();
121 $this->assertContains( 'Kittens', $text );
122 $this->assertContains( '<div', $text );
123 $this->assertContains( 'class="foo bar"', $text );
124
125 $po->addWrapperDivClass( 'bar' ); // second time does nothing, no "foo bar bar".
126 $text = $po->getText( [ 'unwrap' => true ] );
127 $this->assertContains( 'Kittens', $text );
128 $this->assertNotContains( '<div', $text );
129 $this->assertNotContains( 'class="foo bar"', $text );
130
131 $text = $po->getText( [ 'wrapperDivClass' => '' ] );
132 $this->assertContains( 'Kittens', $text );
133 $this->assertNotContains( '<div', $text );
134 $this->assertNotContains( 'class="foo bar"', $text );
135
136 $text = $po->getText( [ 'wrapperDivClass' => 'xyzzy' ] );
137 $this->assertContains( 'Kittens', $text );
138 $this->assertContains( '<div', $text );
139 $this->assertContains( 'class="xyzzy"', $text );
140 $this->assertNotContains( 'class="foo bar"', $text );
141
142 $text = $po->getRawText();
143 $this->assertSame( 'Kittens', $text );
144
145 $po->clearWrapperDivClass();
146 $text = $po->getText();
147 $this->assertContains( 'Kittens', $text );
148 $this->assertNotContains( '<div', $text );
149 $this->assertNotContains( 'class="foo bar"', $text );
150 }
151
159 public function testGetText( $options, $text, $expect ) {
160 $this->setMwGlobals( [
161 'wgArticlePath' => '/wiki/$1',
162 'wgScriptPath' => '/w',
163 'wgScript' => '/w/index.php',
164 ] );
165
166 $po = new ParserOutput( $text );
167 $actual = $po->getText( $options );
168 $this->assertSame( $expect, $actual );
169 }
170
171 public static function provideGetText() {
172 // phpcs:disable Generic.Files.LineLength
173 $text = <<<EOF
174<p>Test document.
175</p>
176<mw:toc><div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
177<ul>
178<li class="toclevel-1 tocsection-1"><a href="#Section_1"><span class="tocnumber">1</span> <span class="toctext">Section 1</span></a></li>
179<li class="toclevel-1 tocsection-2"><a href="#Section_2"><span class="tocnumber">2</span> <span class="toctext">Section 2</span></a>
180<ul>
181<li class="toclevel-2 tocsection-3"><a href="#Section_2.1"><span class="tocnumber">2.1</span> <span class="toctext">Section 2.1</span></a></li>
182</ul>
183</li>
184<li class="toclevel-1 tocsection-4"><a href="#Section_3"><span class="tocnumber">3</span> <span class="toctext">Section 3</span></a></li>
185</ul>
186</div>
187</mw:toc>
188<h2><span class="mw-headline" id="Section_1">Section 1</span><mw:editsection page="Test Page" section="1">Section 1</mw:editsection></h2>
189<p>One
190</p>
191<h2><span class="mw-headline" id="Section_2">Section 2</span><mw:editsection page="Test Page" section="2">Section 2</mw:editsection></h2>
192<p>Two
193</p>
194<h3><span class="mw-headline" id="Section_2.1">Section 2.1</span><mw:editsection page="Test Page" section="3">Section 2.1</mw:editsection></h3>
195<p>Two point one
196</p>
197<h2><span class="mw-headline" id="Section_3">Section 3</span><mw:editsection page="Test Page" section="4">Section 3</mw:editsection></h2>
198<p>Three
199</p>
200EOF;
201
202 $dedupText = <<<EOF
203<p>This is a test document.</p>
204<style data-mw-deduplicate="duplicate1">.Duplicate1 {}</style>
205<style data-mw-deduplicate="duplicate1">.Duplicate1 {}</style>
206<style data-mw-deduplicate="duplicate2">.Duplicate2 {}</style>
207<style data-mw-deduplicate="duplicate1">.Duplicate1 {}</style>
208<style data-mw-deduplicate="duplicate2">.Duplicate2 {}</style>
209<style data-mw-not-deduplicate="duplicate1">.Duplicate1 {}</style>
210<style data-mw-deduplicate="duplicate1">.Same-attribute-different-content {}</style>
211<style data-mw-deduplicate="duplicate3">.Duplicate1 {}</style>
212<style>.Duplicate1 {}</style>
213EOF;
214
215 return [
216 'No options' => [
217 [], $text, <<<EOF
218<p>Test document.
219</p>
220<div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
221<ul>
222<li class="toclevel-1 tocsection-1"><a href="#Section_1"><span class="tocnumber">1</span> <span class="toctext">Section 1</span></a></li>
223<li class="toclevel-1 tocsection-2"><a href="#Section_2"><span class="tocnumber">2</span> <span class="toctext">Section 2</span></a>
224<ul>
225<li class="toclevel-2 tocsection-3"><a href="#Section_2.1"><span class="tocnumber">2.1</span> <span class="toctext">Section 2.1</span></a></li>
226</ul>
227</li>
228<li class="toclevel-1 tocsection-4"><a href="#Section_3"><span class="tocnumber">3</span> <span class="toctext">Section 3</span></a></li>
229</ul>
230</div>
231
232<h2><span class="mw-headline" id="Section_1">Section 1</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&amp;action=edit&amp;section=1" title="Edit section: Section 1">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
233<p>One
234</p>
235<h2><span class="mw-headline" id="Section_2">Section 2</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&amp;action=edit&amp;section=2" title="Edit section: Section 2">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
236<p>Two
237</p>
238<h3><span class="mw-headline" id="Section_2.1">Section 2.1</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&amp;action=edit&amp;section=3" title="Edit section: Section 2.1">edit</a><span class="mw-editsection-bracket">]</span></span></h3>
239<p>Two point one
240</p>
241<h2><span class="mw-headline" id="Section_3">Section 3</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&amp;action=edit&amp;section=4" title="Edit section: Section 3">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
242<p>Three
243</p>
244EOF
245 ],
246 'Disable section edit links' => [
247 [ 'enableSectionEditLinks' => false ], $text, <<<EOF
248<p>Test document.
249</p>
250<div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
251<ul>
252<li class="toclevel-1 tocsection-1"><a href="#Section_1"><span class="tocnumber">1</span> <span class="toctext">Section 1</span></a></li>
253<li class="toclevel-1 tocsection-2"><a href="#Section_2"><span class="tocnumber">2</span> <span class="toctext">Section 2</span></a>
254<ul>
255<li class="toclevel-2 tocsection-3"><a href="#Section_2.1"><span class="tocnumber">2.1</span> <span class="toctext">Section 2.1</span></a></li>
256</ul>
257</li>
258<li class="toclevel-1 tocsection-4"><a href="#Section_3"><span class="tocnumber">3</span> <span class="toctext">Section 3</span></a></li>
259</ul>
260</div>
261
262<h2><span class="mw-headline" id="Section_1">Section 1</span></h2>
263<p>One
264</p>
265<h2><span class="mw-headline" id="Section_2">Section 2</span></h2>
266<p>Two
267</p>
268<h3><span class="mw-headline" id="Section_2.1">Section 2.1</span></h3>
269<p>Two point one
270</p>
271<h2><span class="mw-headline" id="Section_3">Section 3</span></h2>
272<p>Three
273</p>
274EOF
275 ],
276 'Disable TOC, but wrap' => [
277 [ 'allowTOC' => false, 'wrapperDivClass' => 'mw-parser-output' ], $text, <<<EOF
278<div class="mw-parser-output"><p>Test document.
279</p>
280
281<h2><span class="mw-headline" id="Section_1">Section 1</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&amp;action=edit&amp;section=1" title="Edit section: Section 1">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
282<p>One
283</p>
284<h2><span class="mw-headline" id="Section_2">Section 2</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&amp;action=edit&amp;section=2" title="Edit section: Section 2">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
285<p>Two
286</p>
287<h3><span class="mw-headline" id="Section_2.1">Section 2.1</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&amp;action=edit&amp;section=3" title="Edit section: Section 2.1">edit</a><span class="mw-editsection-bracket">]</span></span></h3>
288<p>Two point one
289</p>
290<h2><span class="mw-headline" id="Section_3">Section 3</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&amp;action=edit&amp;section=4" title="Edit section: Section 3">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
291<p>Three
292</p></div>
293EOF
294 ],
295 'Style deduplication' => [
296 [], $dedupText, <<<EOF
297<p>This is a test document.</p>
298<style data-mw-deduplicate="duplicate1">.Duplicate1 {}</style>
299<link rel="mw-deduplicated-inline-style" href="mw-data:duplicate1"/>
300<style data-mw-deduplicate="duplicate2">.Duplicate2 {}</style>
301<link rel="mw-deduplicated-inline-style" href="mw-data:duplicate1"/>
302<link rel="mw-deduplicated-inline-style" href="mw-data:duplicate2"/>
303<style data-mw-not-deduplicate="duplicate1">.Duplicate1 {}</style>
304<link rel="mw-deduplicated-inline-style" href="mw-data:duplicate1"/>
305<style data-mw-deduplicate="duplicate3">.Duplicate1 {}</style>
306<style>.Duplicate1 {}</style>
307EOF
308 ],
309 'Style deduplication disabled' => [
310 [ 'deduplicateStyles' => false ], $dedupText, $dedupText
311 ],
312 ];
313 // phpcs:enable
314 }
315
319 public function testHasText() {
320 $po = new ParserOutput();
321 $this->assertTrue( $po->hasText() );
322
323 $po = new ParserOutput( null );
324 $this->assertFalse( $po->hasText() );
325
326 $po = new ParserOutput( '' );
327 $this->assertTrue( $po->hasText() );
328
329 $po = new ParserOutput( null );
330 $po->setText( '' );
331 $this->assertTrue( $po->hasText() );
332 }
333
337 public function testGetText_failsIfNoText() {
338 $po = new ParserOutput( null );
339
340 $this->setExpectedException( LogicException::class );
341 $po->getText();
342 }
343
348 $po = new ParserOutput( null );
349
350 $this->setExpectedException( LogicException::class );
351 $po->getRawText();
352 }
353
355 // title text ------------
356 $a = new ParserOutput();
357 $a->setTitleText( 'X' );
358 $b = new ParserOutput();
359 yield 'only left title text' => [ $a, $b, [ 'getTitleText' => 'X' ] ];
360
361 $a = new ParserOutput();
362 $b = new ParserOutput();
363 $b->setTitleText( 'Y' );
364 yield 'only right title text' => [ $a, $b, [ 'getTitleText' => 'Y' ] ];
365
366 $a = new ParserOutput();
367 $a->setTitleText( 'X' );
368 $b = new ParserOutput();
369 $b->setTitleText( 'Y' );
370 yield 'left title text wins' => [ $a, $b, [ 'getTitleText' => 'X' ] ];
371
372 // index policy ------------
373 $a = new ParserOutput();
374 $a->setIndexPolicy( 'index' );
375 $b = new ParserOutput();
376 yield 'only left index policy' => [ $a, $b, [ 'getIndexPolicy' => 'index' ] ];
377
378 $a = new ParserOutput();
379 $b = new ParserOutput();
380 $b->setIndexPolicy( 'index' );
381 yield 'only right index policy' => [ $a, $b, [ 'getIndexPolicy' => 'index' ] ];
382
383 $a = new ParserOutput();
384 $a->setIndexPolicy( 'noindex' );
385 $b = new ParserOutput();
386 $b->setIndexPolicy( 'index' );
387 yield 'left noindex wins' => [ $a, $b, [ 'getIndexPolicy' => 'noindex' ] ];
388
389 $a = new ParserOutput();
390 $a->setIndexPolicy( 'index' );
391 $b = new ParserOutput();
392 $b->setIndexPolicy( 'noindex' );
393 yield 'right noindex wins' => [ $a, $b, [ 'getIndexPolicy' => 'noindex' ] ];
394
395 // head items and friends ------------
396 $a = new ParserOutput();
397 $a->addHeadItem( '<foo1>' );
398 $a->addHeadItem( '<bar1>', 'bar' );
399 $a->addModules( 'test-module-a' );
400 $a->addModuleScripts( 'test-module-script-a' );
401 $a->addModuleStyles( 'test-module-styles-a' );
402 $b->addJsConfigVars( 'test-config-var-a', 'a' );
403
404 $b = new ParserOutput();
405 $b->setIndexPolicy( 'noindex' );
406 $b->addHeadItem( '<foo2>' );
407 $b->addHeadItem( '<bar2>', 'bar' );
408 $b->addModules( 'test-module-b' );
409 $b->addModuleScripts( 'test-module-script-b' );
410 $b->addModuleStyles( 'test-module-styles-b' );
411 $b->addJsConfigVars( 'test-config-var-b', 'b' );
412 $b->addJsConfigVars( 'test-config-var-a', 'X' );
413
414 yield 'head items and friends' => [ $a, $b, [
415 'getHeadItems' => [
416 '<foo1>',
417 '<foo2>',
418 'bar' => '<bar2>', // overwritten
419 ],
420 'getModules' => [
421 'test-module-a',
422 'test-module-b',
423 ],
424 'getModuleScripts' => [
425 'test-module-script-a',
426 'test-module-script-b',
427 ],
428 'getModuleStyles' => [
429 'test-module-styles-a',
430 'test-module-styles-b',
431 ],
432 'getJsConfigVars' => [
433 'test-config-var-a' => 'X', // overwritten
434 'test-config-var-b' => 'b',
435 ],
436 ] ];
437
438 // TOC ------------
439 $a = new ParserOutput();
440 $a->setTOCHTML( '<p>TOC A</p>' );
441 $a->setSections( [ [ 'fromtitle' => 'A1' ], [ 'fromtitle' => 'A2' ] ] );
442
443 $b = new ParserOutput();
444 $b->setTOCHTML( '<p>TOC B</p>' );
445 $b->setSections( [ [ 'fromtitle' => 'B1' ], [ 'fromtitle' => 'B2' ] ] );
446
447 yield 'concat TOC' => [ $a, $b, [
448 'getTOCHTML' => '<p>TOC A</p><p>TOC B</p>',
449 'getSections' => [
450 [ 'fromtitle' => 'A1' ],
451 [ 'fromtitle' => 'A2' ],
452 [ 'fromtitle' => 'B1' ],
453 [ 'fromtitle' => 'B2' ]
454 ],
455 ] ];
456
457 // Skin Control ------------
458 $a = new ParserOutput();
459 $a->setNewSection( true );
460 $a->hideNewSection( true );
461 $a->setNoGallery( true );
462 $a->addWrapperDivClass( 'foo' );
463
464 $a->setIndicator( 'foo', 'Foo!' );
465 $a->setIndicator( 'bar', 'Bar!' );
466
467 $a->setExtensionData( 'foo', 'Foo!' );
468 $a->setExtensionData( 'bar', 'Bar!' );
469
470 $b = new ParserOutput();
471 $b->setNoGallery( true );
472 $b->setEnableOOUI( true );
473 $b->preventClickjacking( true );
474 $a->addWrapperDivClass( 'bar' );
475
476 $b->setIndicator( 'zoo', 'Zoo!' );
477 $b->setIndicator( 'bar', 'Barrr!' );
478
479 $b->setExtensionData( 'zoo', 'Zoo!' );
480 $b->setExtensionData( 'bar', 'Barrr!' );
481
482 yield 'skin control flags' => [ $a, $b, [
483 'getNewSection' => true,
484 'getHideNewSection' => true,
485 'getNoGallery' => true,
486 'getEnableOOUI' => true,
487 'preventClickjacking' => true,
488 'getIndicators' => [
489 'foo' => 'Foo!',
490 'bar' => 'Barrr!',
491 'zoo' => 'Zoo!',
492 ],
493 'getWrapperDivClass' => 'foo bar',
494 '$mExtensionData' => [
495 'foo' => 'Foo!',
496 'bar' => 'Barrr!',
497 'zoo' => 'Zoo!',
498 ],
499 ] ];
500 }
501
510 public function testMergeHtmlMetaDataFrom( ParserOutput $a, ParserOutput $b, $expected ) {
511 $a->mergeHtmlMetaDataFrom( $b );
512
513 $this->assertFieldValues( $a, $expected );
514
515 // test twice, to make sure the operation is idempotent (except for the TOC, see below)
516 $a->mergeHtmlMetaDataFrom( $b );
517
518 // XXX: TOC joining should get smarter. Can we make it idempotent as well?
519 unset( $expected['getTOCHTML'] );
520 unset( $expected['getSections'] );
521
522 $this->assertFieldValues( $a, $expected );
523 }
524
525 private function assertFieldValues( ParserOutput $po, $expected ) {
526 $po = TestingAccessWrapper::newFromObject( $po );
527
528 foreach ( $expected as $method => $value ) {
529 if ( $method[0] === '$' ) {
530 $field = substr( $method, 1 );
531 $actual = $po->__get( $field );
532 } else {
533 $actual = $po->__call( $method, [] );
534 }
535
536 $this->assertEquals( $value, $actual, $method );
537 }
538 }
539
541 // links ------------
542 $a = new ParserOutput();
543 $a->addLink( Title::makeTitle( NS_MAIN, 'Kittens' ), 6 );
544 $a->addLink( Title::makeTitle( NS_TALK, 'Kittens' ), 16 );
545 $a->addLink( Title::makeTitle( NS_MAIN, 'Goats' ), 7 );
546
547 $a->addTemplate( Title::makeTitle( NS_TEMPLATE, 'Goats' ), 107, 1107 );
548
549 $a->addLanguageLink( 'de' );
550 $a->addLanguageLink( 'ru' );
551 $a->addInterwikiLink( Title::makeTitle( NS_MAIN, 'Kittens DE', '', 'de' ) );
552 $a->addInterwikiLink( Title::makeTitle( NS_MAIN, 'Kittens RU', '', 'ru' ) );
553 $a->addExternalLink( 'https://kittens.wikimedia.test' );
554 $a->addExternalLink( 'https://goats.wikimedia.test' );
555
556 $a->addCategory( 'Foo', 'X' );
557 $a->addImage( 'Billy.jpg', '20180101000013', 'DEAD' );
558
559 $b = new ParserOutput();
560 $b->addLink( Title::makeTitle( NS_MAIN, 'Goats' ), 7 );
561 $b->addLink( Title::makeTitle( NS_TALK, 'Goats' ), 17 );
562 $b->addLink( Title::makeTitle( NS_MAIN, 'Dragons' ), 8 );
563 $b->addLink( Title::makeTitle( NS_FILE, 'Dragons.jpg' ), 28 );
564
565 $b->addTemplate( Title::makeTitle( NS_TEMPLATE, 'Dragons' ), 108, 1108 );
566 $a->addTemplate( Title::makeTitle( NS_MAIN, 'Dragons' ), 118, 1118 );
567
568 $b->addLanguageLink( 'fr' );
569 $b->addLanguageLink( 'ru' );
570 $b->addInterwikiLink( Title::makeTitle( NS_MAIN, 'Kittens FR', '', 'fr' ) );
571 $b->addInterwikiLink( Title::makeTitle( NS_MAIN, 'Dragons RU', '', 'ru' ) );
572 $b->addExternalLink( 'https://dragons.wikimedia.test' );
573 $b->addExternalLink( 'https://goats.wikimedia.test' );
574
575 $b->addCategory( 'Bar', 'Y' );
576 $b->addImage( 'Puff.jpg', '20180101000017', 'BEEF' );
577
578 yield 'all kinds of links' => [ $a, $b, [
579 'getLinks' => [
580 NS_MAIN => [
581 'Kittens' => 6,
582 'Goats' => 7,
583 'Dragons' => 8,
584 ],
585 NS_TALK => [
586 'Kittens' => 16,
587 'Goats' => 17,
588 ],
589 NS_FILE => [
590 'Dragons.jpg' => 28,
591 ],
592 ],
593 'getTemplates' => [
594 NS_MAIN => [
595 'Dragons' => 118,
596 ],
597 NS_TEMPLATE => [
598 'Dragons' => 108,
599 'Goats' => 107,
600 ],
601 ],
602 'getTemplateIds' => [
603 NS_MAIN => [
604 'Dragons' => 1118,
605 ],
606 NS_TEMPLATE => [
607 'Dragons' => 1108,
608 'Goats' => 1107,
609 ],
610 ],
611 'getLanguageLinks' => [ 'de', 'ru', 'fr' ],
612 'getInterwikiLinks' => [
613 'de' => [ 'Kittens_DE' => 1 ],
614 'ru' => [ 'Kittens_RU' => 1, 'Dragons_RU' => 1, ],
615 'fr' => [ 'Kittens_FR' => 1 ],
616 ],
617 'getCategories' => [ 'Foo' => 'X', 'Bar' => 'Y' ],
618 'getImages' => [ 'Billy.jpg' => 1, 'Puff.jpg' => 1 ],
619 'getFileSearchOptions' => [
620 'Billy.jpg' => [ 'time' => '20180101000013', 'sha1' => 'DEAD' ],
621 'Puff.jpg' => [ 'time' => '20180101000017', 'sha1' => 'BEEF' ],
622 ],
623 'getExternalLinks' => [
624 'https://dragons.wikimedia.test' => 1,
625 'https://kittens.wikimedia.test' => 1,
626 'https://goats.wikimedia.test' => 1,
627 ]
628 ] ];
629
630 // properties ------------
631 $a = new ParserOutput();
632
633 $a->setProperty( 'foo', 'Foo!' );
634 $a->setProperty( 'bar', 'Bar!' );
635
636 $a->setExtensionData( 'foo', 'Foo!' );
637 $a->setExtensionData( 'bar', 'Bar!' );
638
639 $b = new ParserOutput();
640
641 $b->setProperty( 'zoo', 'Zoo!' );
642 $b->setProperty( 'bar', 'Barrr!' );
643
644 $b->setExtensionData( 'zoo', 'Zoo!' );
645 $b->setExtensionData( 'bar', 'Barrr!' );
646
647 yield 'properties' => [ $a, $b, [
648 'getProperties' => [
649 'foo' => 'Foo!',
650 'bar' => 'Barrr!',
651 'zoo' => 'Zoo!',
652 ],
653 '$mExtensionData' => [
654 'foo' => 'Foo!',
655 'bar' => 'Barrr!',
656 'zoo' => 'Zoo!',
657 ],
658 ] ];
659 }
660
669 public function testMergeTrackingMetaDataFrom( ParserOutput $a, ParserOutput $b, $expected ) {
671
672 $this->assertFieldValues( $a, $expected );
673
674 // test twice, to make sure the operation is idempotent
676
677 $this->assertFieldValues( $a, $expected );
678 }
679
681 // hooks
682 $a = new ParserOutput();
683
684 $a->addOutputHook( 'foo', 'X' );
685 $a->addOutputHook( 'bar' );
686
687 $b = new ParserOutput();
688
689 $b->addOutputHook( 'foo', 'Y' );
690 $b->addOutputHook( 'bar' );
691 $b->addOutputHook( 'zoo' );
692
693 yield 'hooks' => [ $a, $b, [
694 'getOutputHooks' => [
695 [ 'foo', 'X' ],
696 [ 'bar', false ],
697 [ 'foo', 'Y' ],
698 [ 'zoo', false ],
699 ],
700 ] ];
701
702 // flags & co
703 $a = new ParserOutput();
704
705 $a->addWarning( 'Oops' );
706 $a->addWarning( 'Whoops' );
707
708 $a->setFlag( 'foo' );
709 $a->setFlag( 'bar' );
710
711 $a->recordOption( 'Foo' );
712 $a->recordOption( 'Bar' );
713
714 $b = new ParserOutput();
715
716 $b->addWarning( 'Yikes' );
717 $b->addWarning( 'Whoops' );
718
719 $b->setFlag( 'zoo' );
720 $b->setFlag( 'bar' );
721
722 $b->recordOption( 'Zoo' );
723 $b->recordOption( 'Bar' );
724
725 yield 'flags' => [ $a, $b, [
726 'getWarnings' => [ 'Oops', 'Whoops', 'Yikes' ],
727 '$mFlags' => [ 'foo' => true, 'bar' => true, 'zoo' => true ],
728 'getUsedOptions' => [ 'Foo', 'Bar', 'Zoo' ],
729 ] ];
730
731 // timestamp ------------
732 $a = new ParserOutput();
733 $a->setTimestamp( '20180101000011' );
734 $b = new ParserOutput();
735 yield 'only left timestamp' => [ $a, $b, [ 'getTimestamp' => '20180101000011' ] ];
736
737 $a = new ParserOutput();
738 $b = new ParserOutput();
739 $b->setTimestamp( '20180101000011' );
740 yield 'only right timestamp' => [ $a, $b, [ 'getTimestamp' => '20180101000011' ] ];
741
742 $a = new ParserOutput();
743 $a->setTimestamp( '20180101000011' );
744 $b = new ParserOutput();
745 $b->setTimestamp( '20180101000001' );
746 yield 'left timestamp wins' => [ $a, $b, [ 'getTimestamp' => '20180101000011' ] ];
747
748 $a = new ParserOutput();
749 $a->setTimestamp( '20180101000001' );
750 $b = new ParserOutput();
751 $b->setTimestamp( '20180101000011' );
752 yield 'right timestamp wins' => [ $a, $b, [ 'getTimestamp' => '20180101000011' ] ];
753
754 // speculative rev id ------------
755 $a = new ParserOutput();
756 $a->setSpeculativeRevIdUsed( 9 );
757 $b = new ParserOutput();
758 yield 'only left speculative rev id' => [ $a, $b, [ 'getSpeculativeRevIdUsed' => 9 ] ];
759
760 $a = new ParserOutput();
761 $b = new ParserOutput();
762 $b->setSpeculativeRevIdUsed( 9 );
763 yield 'only right speculative rev id' => [ $a, $b, [ 'getSpeculativeRevIdUsed' => 9 ] ];
764
765 $a = new ParserOutput();
766 $a->setSpeculativeRevIdUsed( 9 );
767 $b = new ParserOutput();
768 $b->setSpeculativeRevIdUsed( 9 );
769 yield 'same speculative rev id' => [ $a, $b, [ 'getSpeculativeRevIdUsed' => 9 ] ];
770
771 // limit report (recursive max) ------------
772 $a = new ParserOutput();
773
774 $a->setLimitReportData( 'naive1', 7 );
775 $a->setLimitReportData( 'naive2', 27 );
776
777 $a->setLimitReportData( 'limitreport-simple1', 7 );
778 $a->setLimitReportData( 'limitreport-simple2', 27 );
779
780 $a->setLimitReportData( 'limitreport-pair1', [ 7, 9 ] );
781 $a->setLimitReportData( 'limitreport-pair2', [ 27, 29 ] );
782
783 $a->setLimitReportData( 'limitreport-more1', [ 7, 9, 1 ] );
784 $a->setLimitReportData( 'limitreport-more2', [ 27, 29, 21 ] );
785
786 $a->setLimitReportData( 'limitreport-only-a', 13 );
787
788 $b = new ParserOutput();
789
790 $b->setLimitReportData( 'naive1', 17 );
791 $b->setLimitReportData( 'naive2', 17 );
792
793 $b->setLimitReportData( 'limitreport-simple1', 17 );
794 $b->setLimitReportData( 'limitreport-simple2', 17 );
795
796 $b->setLimitReportData( 'limitreport-pair1', [ 17, 19 ] );
797 $b->setLimitReportData( 'limitreport-pair2', [ 17, 19 ] );
798
799 $b->setLimitReportData( 'limitreport-more1', [ 17, 19, 11 ] );
800 $b->setLimitReportData( 'limitreport-more2', [ 17, 19, 11 ] );
801
802 $b->setLimitReportData( 'limitreport-only-b', 23 );
803
804 // first write wins
805 yield 'limit report' => [ $a, $b, [
806 'getLimitReportData' => [
807 'naive1' => 7,
808 'naive2' => 27,
809 'limitreport-simple1' => 7,
810 'limitreport-simple2' => 27,
811 'limitreport-pair1' => [ 7, 9 ],
812 'limitreport-pair2' => [ 27, 29 ],
813 'limitreport-more1' => [ 7, 9, 1 ],
814 'limitreport-more2' => [ 27, 29, 21 ],
815 'limitreport-only-a' => 13,
816 ],
817 'getLimitReportJSData' => [
818 'naive1' => 7,
819 'naive2' => 27,
820 'limitreport' => [
821 'simple1' => 7,
822 'simple2' => 27,
823 'pair1' => [ 'value' => 7, 'limit' => 9 ],
824 'pair2' => [ 'value' => 27, 'limit' => 29 ],
825 'more1' => [ 7, 9, 1 ],
826 'more2' => [ 27, 29, 21 ],
827 'only-a' => 13,
828 ],
829 ],
830 ] ];
831 }
832
841 public function testMergeInternalMetaDataFrom( ParserOutput $a, ParserOutput $b, $expected ) {
843
844 $this->assertFieldValues( $a, $expected );
845
846 // test twice, to make sure the operation is idempotent
848
849 $this->assertFieldValues( $a, $expected );
850 }
851
854 $a = new ParserOutput();
855 $a = TestingAccessWrapper::newFromObject( $a );
856
857 $a->resetParseStartTime();
858 $aClocks = $a->mParseStartTime;
859
860 $b = new ParserOutput();
861
862 $a->mergeInternalMetaDataFrom( $b );
863 $mergedClocks = $a->mParseStartTime;
864
865 foreach ( $mergedClocks as $clock => $timestamp ) {
866 $this->assertSame( $aClocks[$clock], $timestamp, $clock );
867 }
868
869 // try again, with times in $b also set, and later than $a's
870 usleep( 1234 );
871
873 $b = new ParserOutput();
874 $b = TestingAccessWrapper::newFromObject( $b );
875
876 $b->resetParseStartTime();
877
878 $bClocks = $b->mParseStartTime;
879
880 $a->mergeInternalMetaDataFrom( $b->object, 'b' );
881 $mergedClocks = $a->mParseStartTime;
882
883 foreach ( $mergedClocks as $clock => $timestamp ) {
884 $this->assertSame( $aClocks[$clock], $timestamp, $clock );
885 $this->assertLessThanOrEqual( $bClocks[$clock], $timestamp, $clock );
886 }
887
888 // try again, with $a's times being later
889 usleep( 1234 );
890 $a->resetParseStartTime();
891 $aClocks = $a->mParseStartTime;
892
893 $a->mergeInternalMetaDataFrom( $b->object, 'b' );
894 $mergedClocks = $a->mParseStartTime;
895
896 foreach ( $mergedClocks as $clock => $timestamp ) {
897 $this->assertSame( $bClocks[$clock], $timestamp, $clock );
898 $this->assertLessThanOrEqual( $aClocks[$clock], $timestamp, $clock );
899 }
900
901 // try again, with no times in $a set
902 $a = new ParserOutput();
903 $a = TestingAccessWrapper::newFromObject( $a );
904
905 $a->mergeInternalMetaDataFrom( $b->object, 'b' );
906 $mergedClocks = $a->mParseStartTime;
907
908 foreach ( $mergedClocks as $clock => $timestamp ) {
909 $this->assertSame( $bClocks[$clock], $timestamp, $clock );
910 }
911 }
912
917 public function testGetCacheTime() {
918 $clock = MWTimestamp::convert( TS_UNIX, '20100101000000' );
919 MWTimestamp::setFakeTime( function () use ( &$clock ) {
920 return $clock++;
921 } );
922
923 $po = new ParserOutput();
924 $time = $po->getCacheTime();
925
926 // Use current (fake) time per default. Ignore the last digit.
927 // Subsequent calls must yield the exact same timestamp as the first.
928 $this->assertStringStartsWith( '2010010100000', $time );
929 $this->assertSame( $time, $po->getCacheTime() );
930
931 // After setting, the getter must return the time that was set.
932 $time = '20110606112233';
933 $po->setCacheTime( $time );
934 $this->assertSame( $time, $po->getCacheTime() );
935
936 // support -1 as a marker for "not cacheable"
937 $time = -1;
938 $po->setCacheTime( $time );
939 $this->assertSame( $time, $po->getCacheTime() );
940 }
941
942}
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
shown</td >< td > a href
target page
Base class that store and restore the Language objects.
setMwGlobals( $pairs, $value=null)
Sets a global, maintaining a stashed version of the previous global to be restored in tearDown.
Database ^— trigger DB shadowing because we are using Title magic.
testMergeInternalMetaDataFrom_parseStartTime()
testGetText( $options, $text, $expect)
ParserOutput::getText provideGetText.
assertFieldValues(ParserOutput $po, $expected)
testProperties()
ParserOutput::setProperty ParserOutput::getProperty ParserOutput::unsetProperty ParserOutput::getProp...
testGetRawText_failsIfNoText()
ParserOutput::getRawText.
testMergeHtmlMetaDataFrom(ParserOutput $a, ParserOutput $b, $expected)
provideMergeHtmlMetaDataFrom ParserOutput::mergeHtmlMetaDataFrom
testMergeTrackingMetaDataFrom(ParserOutput $a, ParserOutput $b, $expected)
provideMergeTrackingMetaDataFrom ParserOutput::mergeTrackingMetaDataFrom
static provideIsLinkInternal()
testExtensionData()
ParserOutput::setExtensionData ParserOutput::getExtensionData.
testMergeInternalMetaDataFrom(ParserOutput $a, ParserOutput $b, $expected)
provideMergeInternalMetaDataFrom ParserOutput::mergeInternalMetaDataFrom
testHasText()
ParserOutput::hasText.
testWrapperDivClass()
ParserOutput::getWrapperDivClass ParserOutput::addWrapperDivClass ParserOutput::clearWrapperDivClass ...
testGetText_failsIfNoText()
ParserOutput::getText.
testIsLinkInternal( $shouldMatch, $server, $url)
Test to make sure ParserOutput::isLinkInternal behaves properly provideIsLinkInternal ParserOutput::i...
testGetCacheTime()
ParserOutput::getCacheTime ParserOutput::setCacheTime.
mergeHtmlMetaDataFrom(ParserOutput $source)
Merges HTML metadata such as head items, JS config vars, and HTTP cache control info from $source int...
mergeTrackingMetaDataFrom(ParserOutput $source)
Merges dependency tracking metadata such as backlinks, images used, and extension data from $source i...
mergeInternalMetaDataFrom(ParserOutput $source)
Merges internal metadata such as flags, accessed options, and profiling info from $source into this P...
per default it will return the text for text based content
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
const NS_FILE
Definition Defines.php:70
const NS_MAIN
Definition Defines.php:64
const NS_TEMPLATE
Definition Defines.php:74
const NS_TALK
Definition Defines.php:65
see documentation in includes Linker php for Linker::makeImageLink & $time
Definition hooks.txt:1841
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped & $options
Definition hooks.txt:2050
usually copyright or history_copyright This message must be in HTML not wikitext if the section is included from a template to be included in the link
Definition hooks.txt:3108
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return true
Definition hooks.txt:2055
processing should stop and the error should be shown to the user * false
Definition hooks.txt:187
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition injection.txt:37
This document provides an overview of the usage of PageUpdater and that is
title
Bar style
This document describes the state of Postgres support in and is fairly well maintained The main code is very well while extensions are very hit and miss it is probably the most supported database after MySQL Much of the work in making MediaWiki database agnostic came about through the work of creating Postgres as and are nearing end of but without copying over all the usage comments General notes on the but these can almost always be programmed around *Although Postgres has a true BOOLEAN boolean columns are always mapped to as the code does not always treat the column as a and VARBINARY columns should simply be TEXT The only exception is when VARBINARY is used to store true binary data
Definition postgres.txt:43