MediaWiki  master
ParserOutputTest.php
Go to the documentation of this file.
1 <?php
2 
4 
10 
11  public static function provideIsLinkInternal() {
12  return [
13  // Different domains
14  [ false, 'http://example.org', 'http://mediawiki.org' ],
15  // Same domains
16  [ true, 'http://example.org', 'http://example.org' ],
17  [ true, 'https://example.org', 'https://example.org' ],
18  [ true, '//example.org', '//example.org' ],
19  // Same domain different cases
20  [ true, 'http://example.org', 'http://EXAMPLE.ORG' ],
21  // Paths, queries, and fragments are not relevant
22  [ true, 'http://example.org', 'http://example.org/wiki/Main_Page' ],
23  [ true, 'http://example.org', 'http://example.org?my=query' ],
24  [ true, 'http://example.org', 'http://example.org#its-a-fragment' ],
25  // Different protocols
26  [ false, 'http://example.org', 'https://example.org' ],
27  [ false, 'https://example.org', 'http://example.org' ],
28  // Protocol relative servers always match http and https links
29  [ true, '//example.org', 'http://example.org' ],
30  [ true, '//example.org', 'https://example.org' ],
31  // But they don't match strange things like this
32  [ false, '//example.org', 'irc://example.org' ],
33  ];
34  }
35 
36  public function tearDown() {
37  MWTimestamp::setFakeTime( false );
38 
39  parent::tearDown();
40  }
41 
47  public function testIsLinkInternal( $shouldMatch, $server, $url ) {
48  $this->assertEquals( $shouldMatch, ParserOutput::isLinkInternal( $server, $url ) );
49  }
50 
55  public function testExtensionData() {
56  $po = new ParserOutput();
57 
58  $po->setExtensionData( "one", "Foo" );
59 
60  $this->assertEquals( "Foo", $po->getExtensionData( "one" ) );
61  $this->assertNull( $po->getExtensionData( "spam" ) );
62 
63  $po->setExtensionData( "two", "Bar" );
64  $this->assertEquals( "Foo", $po->getExtensionData( "one" ) );
65  $this->assertEquals( "Bar", $po->getExtensionData( "two" ) );
66 
67  $po->setExtensionData( "one", null );
68  $this->assertNull( $po->getExtensionData( "one" ) );
69  $this->assertEquals( "Bar", $po->getExtensionData( "two" ) );
70  }
71 
78  public function testProperties() {
79  $po = new ParserOutput();
80 
81  $po->setProperty( 'foo', 'val' );
82 
83  $properties = $po->getProperties();
84  $this->assertEquals( $po->getProperty( 'foo' ), 'val' );
85  $this->assertEquals( $properties['foo'], 'val' );
86 
87  $po->setProperty( 'foo', 'second val' );
88 
89  $properties = $po->getProperties();
90  $this->assertEquals( $po->getProperty( 'foo' ), 'second val' );
91  $this->assertEquals( $properties['foo'], 'second val' );
92 
93  $po->unsetProperty( 'foo' );
94 
95  $properties = $po->getProperties();
96  $this->assertEquals( $po->getProperty( 'foo' ), false );
97  $this->assertArrayNotHasKey( 'foo', $properties );
98  }
99 
106  public function testWrapperDivClass() {
107  $po = new ParserOutput();
108 
109  $po->setText( 'Kittens' );
110  $this->assertContains( 'Kittens', $po->getText() );
111  $this->assertNotContains( '<div', $po->getText() );
112  $this->assertSame( 'Kittens', $po->getRawText() );
113 
114  $po->addWrapperDivClass( 'foo' );
115  $text = $po->getText();
116  $this->assertContains( 'Kittens', $text );
117  $this->assertContains( '<div', $text );
118  $this->assertContains( 'class="foo"', $text );
119 
120  $po->addWrapperDivClass( 'bar' );
121  $text = $po->getText();
122  $this->assertContains( 'Kittens', $text );
123  $this->assertContains( '<div', $text );
124  $this->assertContains( 'class="foo bar"', $text );
125 
126  $po->addWrapperDivClass( 'bar' ); // second time does nothing, no "foo bar bar".
127  $text = $po->getText( [ 'unwrap' => true ] );
128  $this->assertContains( 'Kittens', $text );
129  $this->assertNotContains( '<div', $text );
130  $this->assertNotContains( 'class="foo bar"', $text );
131 
132  $text = $po->getText( [ 'wrapperDivClass' => '' ] );
133  $this->assertContains( 'Kittens', $text );
134  $this->assertNotContains( '<div', $text );
135  $this->assertNotContains( 'class="foo bar"', $text );
136 
137  $text = $po->getText( [ 'wrapperDivClass' => 'xyzzy' ] );
138  $this->assertContains( 'Kittens', $text );
139  $this->assertContains( '<div', $text );
140  $this->assertContains( 'class="xyzzy"', $text );
141  $this->assertNotContains( 'class="foo bar"', $text );
142 
143  $text = $po->getRawText();
144  $this->assertSame( 'Kittens', $text );
145 
146  $po->clearWrapperDivClass();
147  $text = $po->getText();
148  $this->assertContains( 'Kittens', $text );
149  $this->assertNotContains( '<div', $text );
150  $this->assertNotContains( 'class="foo bar"', $text );
151  }
152 
160  public function testGetText( $options, $text, $expect ) {
161  $this->setMwGlobals( [
162  'wgArticlePath' => '/wiki/$1',
163  'wgScriptPath' => '/w',
164  'wgScript' => '/w/index.php',
165  ] );
166 
167  $po = new ParserOutput( $text );
168  $actual = $po->getText( $options );
169  $this->assertSame( $expect, $actual );
170  }
171 
172  public static function provideGetText() {
173  // phpcs:disable Generic.Files.LineLength
174  $text = <<<EOF
175 <p>Test document.
176 </p>
177 <mw:toc><div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
178 <ul>
179 <li class="toclevel-1 tocsection-1"><a href="#Section_1"><span class="tocnumber">1</span> <span class="toctext">Section 1</span></a></li>
180 <li class="toclevel-1 tocsection-2"><a href="#Section_2"><span class="tocnumber">2</span> <span class="toctext">Section 2</span></a>
181 <ul>
182 <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>
183 </ul>
184 </li>
185 <li class="toclevel-1 tocsection-4"><a href="#Section_3"><span class="tocnumber">3</span> <span class="toctext">Section 3</span></a></li>
186 </ul>
187 </div>
188 </mw:toc>
189 <h2><span class="mw-headline" id="Section_1">Section 1</span><mw:editsection page="Test Page" section="1">Section 1</mw:editsection></h2>
190 <p>One
191 </p>
192 <h2><span class="mw-headline" id="Section_2">Section 2</span><mw:editsection page="Test Page" section="2">Section 2</mw:editsection></h2>
193 <p>Two
194 </p>
195 <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>
196 <p>Two point one
197 </p>
198 <h2><span class="mw-headline" id="Section_3">Section 3</span><mw:editsection page="Test Page" section="4">Section 3</mw:editsection></h2>
199 <p>Three
200 </p>
201 EOF;
202 
203  $dedupText = <<<EOF
204 <p>This is a test document.</p>
205 <style data-mw-deduplicate="duplicate1">.Duplicate1 {}</style>
206 <style data-mw-deduplicate="duplicate1">.Duplicate1 {}</style>
207 <style data-mw-deduplicate="duplicate2">.Duplicate2 {}</style>
208 <style data-mw-deduplicate="duplicate1">.Duplicate1 {}</style>
209 <style data-mw-deduplicate="duplicate2">.Duplicate2 {}</style>
210 <style data-mw-not-deduplicate="duplicate1">.Duplicate1 {}</style>
211 <style data-mw-deduplicate="duplicate1">.Same-attribute-different-content {}</style>
212 <style data-mw-deduplicate="duplicate3">.Duplicate1 {}</style>
213 <style>.Duplicate1 {}</style>
214 EOF;
215 
216  return [
217  'No options' => [
218  [], $text, <<<EOF
219 <p>Test document.
220 </p>
221 <div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
222 <ul>
223 <li class="toclevel-1 tocsection-1"><a href="#Section_1"><span class="tocnumber">1</span> <span class="toctext">Section 1</span></a></li>
224 <li class="toclevel-1 tocsection-2"><a href="#Section_2"><span class="tocnumber">2</span> <span class="toctext">Section 2</span></a>
225 <ul>
226 <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>
227 </ul>
228 </li>
229 <li class="toclevel-1 tocsection-4"><a href="#Section_3"><span class="tocnumber">3</span> <span class="toctext">Section 3</span></a></li>
230 </ul>
231 </div>
232 
233 <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>
234 <p>One
235 </p>
236 <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>
237 <p>Two
238 </p>
239 <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>
240 <p>Two point one
241 </p>
242 <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>
243 <p>Three
244 </p>
245 EOF
246  ],
247  'Disable section edit links' => [
248  [ 'enableSectionEditLinks' => false ], $text, <<<EOF
249 <p>Test document.
250 </p>
251 <div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
252 <ul>
253 <li class="toclevel-1 tocsection-1"><a href="#Section_1"><span class="tocnumber">1</span> <span class="toctext">Section 1</span></a></li>
254 <li class="toclevel-1 tocsection-2"><a href="#Section_2"><span class="tocnumber">2</span> <span class="toctext">Section 2</span></a>
255 <ul>
256 <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>
257 </ul>
258 </li>
259 <li class="toclevel-1 tocsection-4"><a href="#Section_3"><span class="tocnumber">3</span> <span class="toctext">Section 3</span></a></li>
260 </ul>
261 </div>
262 
263 <h2><span class="mw-headline" id="Section_1">Section 1</span></h2>
264 <p>One
265 </p>
266 <h2><span class="mw-headline" id="Section_2">Section 2</span></h2>
267 <p>Two
268 </p>
269 <h3><span class="mw-headline" id="Section_2.1">Section 2.1</span></h3>
270 <p>Two point one
271 </p>
272 <h2><span class="mw-headline" id="Section_3">Section 3</span></h2>
273 <p>Three
274 </p>
275 EOF
276  ],
277  'Disable TOC, but wrap' => [
278  [ 'allowTOC' => false, 'wrapperDivClass' => 'mw-parser-output' ], $text, <<<EOF
279 <div class="mw-parser-output"><p>Test document.
280 </p>
281 
282 <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>
283 <p>One
284 </p>
285 <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>
286 <p>Two
287 </p>
288 <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>
289 <p>Two point one
290 </p>
291 <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>
292 <p>Three
293 </p></div>
294 EOF
295  ],
296  'Style deduplication' => [
297  [], $dedupText, <<<EOF
298 <p>This is a test document.</p>
299 <style data-mw-deduplicate="duplicate1">.Duplicate1 {}</style>
300 <link rel="mw-deduplicated-inline-style" href="mw-data:duplicate1"/>
301 <style data-mw-deduplicate="duplicate2">.Duplicate2 {}</style>
302 <link rel="mw-deduplicated-inline-style" href="mw-data:duplicate1"/>
303 <link rel="mw-deduplicated-inline-style" href="mw-data:duplicate2"/>
304 <style data-mw-not-deduplicate="duplicate1">.Duplicate1 {}</style>
305 <link rel="mw-deduplicated-inline-style" href="mw-data:duplicate1"/>
306 <style data-mw-deduplicate="duplicate3">.Duplicate1 {}</style>
307 <style>.Duplicate1 {}</style>
308 EOF
309  ],
310  'Style deduplication disabled' => [
311  [ 'deduplicateStyles' => false ], $dedupText, $dedupText
312  ],
313  ];
314  // phpcs:enable
315  }
316 
320  public function testHasText() {
321  $po = new ParserOutput();
322  $this->assertTrue( $po->hasText() );
323 
324  $po = new ParserOutput( null );
325  $this->assertFalse( $po->hasText() );
326 
327  $po = new ParserOutput( '' );
328  $this->assertTrue( $po->hasText() );
329 
330  $po = new ParserOutput( null );
331  $po->setText( '' );
332  $this->assertTrue( $po->hasText() );
333  }
334 
338  public function testGetText_failsIfNoText() {
339  $po = new ParserOutput( null );
340 
341  $this->setExpectedException( LogicException::class );
342  $po->getText();
343  }
344 
348  public function testGetRawText_failsIfNoText() {
349  $po = new ParserOutput( null );
350 
351  $this->setExpectedException( LogicException::class );
352  $po->getRawText();
353  }
354 
355  public function provideMergeHtmlMetaDataFrom() {
356  // title text ------------
357  $a = new ParserOutput();
358  $a->setTitleText( 'X' );
359  $b = new ParserOutput();
360  yield 'only left title text' => [ $a, $b, [ 'getTitleText' => 'X' ] ];
361 
362  $a = new ParserOutput();
363  $b = new ParserOutput();
364  $b->setTitleText( 'Y' );
365  yield 'only right title text' => [ $a, $b, [ 'getTitleText' => 'Y' ] ];
366 
367  $a = new ParserOutput();
368  $a->setTitleText( 'X' );
369  $b = new ParserOutput();
370  $b->setTitleText( 'Y' );
371  yield 'left title text wins' => [ $a, $b, [ 'getTitleText' => 'X' ] ];
372 
373  // index policy ------------
374  $a = new ParserOutput();
375  $a->setIndexPolicy( 'index' );
376  $b = new ParserOutput();
377  yield 'only left index policy' => [ $a, $b, [ 'getIndexPolicy' => 'index' ] ];
378 
379  $a = new ParserOutput();
380  $b = new ParserOutput();
381  $b->setIndexPolicy( 'index' );
382  yield 'only right index policy' => [ $a, $b, [ 'getIndexPolicy' => 'index' ] ];
383 
384  $a = new ParserOutput();
385  $a->setIndexPolicy( 'noindex' );
386  $b = new ParserOutput();
387  $b->setIndexPolicy( 'index' );
388  yield 'left noindex wins' => [ $a, $b, [ 'getIndexPolicy' => 'noindex' ] ];
389 
390  $a = new ParserOutput();
391  $a->setIndexPolicy( 'index' );
392  $b = new ParserOutput();
393  $b->setIndexPolicy( 'noindex' );
394  yield 'right noindex wins' => [ $a, $b, [ 'getIndexPolicy' => 'noindex' ] ];
395 
396  // head items and friends ------------
397  $a = new ParserOutput();
398  $a->addHeadItem( '<foo1>' );
399  $a->addHeadItem( '<bar1>', 'bar' );
400  $a->addModules( 'test-module-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->addModuleStyles( 'test-module-styles-b' );
410  $b->addJsConfigVars( 'test-config-var-b', 'b' );
411  $b->addJsConfigVars( 'test-config-var-a', 'X' );
412 
413  yield 'head items and friends' => [ $a, $b, [
414  'getHeadItems' => [
415  '<foo1>',
416  '<foo2>',
417  'bar' => '<bar2>', // overwritten
418  ],
419  'getModules' => [
420  'test-module-a',
421  'test-module-b',
422  ],
423  'getModuleStyles' => [
424  'test-module-styles-a',
425  'test-module-styles-b',
426  ],
427  'getJsConfigVars' => [
428  'test-config-var-a' => 'X', // overwritten
429  'test-config-var-b' => 'b',
430  ],
431  ] ];
432 
433  // TOC ------------
434  $a = new ParserOutput();
435  $a->setTOCHTML( '<p>TOC A</p>' );
436  $a->setSections( [ [ 'fromtitle' => 'A1' ], [ 'fromtitle' => 'A2' ] ] );
437 
438  $b = new ParserOutput();
439  $b->setTOCHTML( '<p>TOC B</p>' );
440  $b->setSections( [ [ 'fromtitle' => 'B1' ], [ 'fromtitle' => 'B2' ] ] );
441 
442  yield 'concat TOC' => [ $a, $b, [
443  'getTOCHTML' => '<p>TOC A</p><p>TOC B</p>',
444  'getSections' => [
445  [ 'fromtitle' => 'A1' ],
446  [ 'fromtitle' => 'A2' ],
447  [ 'fromtitle' => 'B1' ],
448  [ 'fromtitle' => 'B2' ]
449  ],
450  ] ];
451 
452  // Skin Control ------------
453  $a = new ParserOutput();
454  $a->setNewSection( true );
455  $a->hideNewSection( true );
456  $a->setNoGallery( true );
457  $a->addWrapperDivClass( 'foo' );
458 
459  $a->setIndicator( 'foo', 'Foo!' );
460  $a->setIndicator( 'bar', 'Bar!' );
461 
462  $a->setExtensionData( 'foo', 'Foo!' );
463  $a->setExtensionData( 'bar', 'Bar!' );
464 
465  $b = new ParserOutput();
466  $b->setNoGallery( true );
467  $b->setEnableOOUI( true );
468  $b->preventClickjacking( true );
469  $a->addWrapperDivClass( 'bar' );
470 
471  $b->setIndicator( 'zoo', 'Zoo!' );
472  $b->setIndicator( 'bar', 'Barrr!' );
473 
474  $b->setExtensionData( 'zoo', 'Zoo!' );
475  $b->setExtensionData( 'bar', 'Barrr!' );
476 
477  yield 'skin control flags' => [ $a, $b, [
478  'getNewSection' => true,
479  'getHideNewSection' => true,
480  'getNoGallery' => true,
481  'getEnableOOUI' => true,
482  'preventClickjacking' => true,
483  'getIndicators' => [
484  'foo' => 'Foo!',
485  'bar' => 'Barrr!',
486  'zoo' => 'Zoo!',
487  ],
488  'getWrapperDivClass' => 'foo bar',
489  '$mExtensionData' => [
490  'foo' => 'Foo!',
491  'bar' => 'Barrr!',
492  'zoo' => 'Zoo!',
493  ],
494  ] ];
495  }
496 
505  public function testMergeHtmlMetaDataFrom( ParserOutput $a, ParserOutput $b, $expected ) {
506  $a->mergeHtmlMetaDataFrom( $b );
507 
508  $this->assertFieldValues( $a, $expected );
509 
510  // test twice, to make sure the operation is idempotent (except for the TOC, see below)
511  $a->mergeHtmlMetaDataFrom( $b );
512 
513  // XXX: TOC joining should get smarter. Can we make it idempotent as well?
514  unset( $expected['getTOCHTML'] );
515  unset( $expected['getSections'] );
516 
517  $this->assertFieldValues( $a, $expected );
518  }
519 
520  private function assertFieldValues( ParserOutput $po, $expected ) {
521  $po = TestingAccessWrapper::newFromObject( $po );
522 
523  foreach ( $expected as $method => $value ) {
524  if ( $method[0] === '$' ) {
525  $field = substr( $method, 1 );
526  $actual = $po->__get( $field );
527  } else {
528  $actual = $po->__call( $method, [] );
529  }
530 
531  $this->assertEquals( $value, $actual, $method );
532  }
533  }
534 
536  // links ------------
537  $a = new ParserOutput();
538  $a->addLink( Title::makeTitle( NS_MAIN, 'Kittens' ), 6 );
539  $a->addLink( Title::makeTitle( NS_TALK, 'Kittens' ), 16 );
540  $a->addLink( Title::makeTitle( NS_MAIN, 'Goats' ), 7 );
541 
542  $a->addTemplate( Title::makeTitle( NS_TEMPLATE, 'Goats' ), 107, 1107 );
543 
544  $a->addLanguageLink( 'de' );
545  $a->addLanguageLink( 'ru' );
546  $a->addInterwikiLink( Title::makeTitle( NS_MAIN, 'Kittens DE', '', 'de' ) );
547  $a->addInterwikiLink( Title::makeTitle( NS_MAIN, 'Kittens RU', '', 'ru' ) );
548  $a->addExternalLink( 'https://kittens.wikimedia.test' );
549  $a->addExternalLink( 'https://goats.wikimedia.test' );
550 
551  $a->addCategory( 'Foo', 'X' );
552  $a->addImage( 'Billy.jpg', '20180101000013', 'DEAD' );
553 
554  $b = new ParserOutput();
555  $b->addLink( Title::makeTitle( NS_MAIN, 'Goats' ), 7 );
556  $b->addLink( Title::makeTitle( NS_TALK, 'Goats' ), 17 );
557  $b->addLink( Title::makeTitle( NS_MAIN, 'Dragons' ), 8 );
558  $b->addLink( Title::makeTitle( NS_FILE, 'Dragons.jpg' ), 28 );
559 
560  $b->addTemplate( Title::makeTitle( NS_TEMPLATE, 'Dragons' ), 108, 1108 );
561  $a->addTemplate( Title::makeTitle( NS_MAIN, 'Dragons' ), 118, 1118 );
562 
563  $b->addLanguageLink( 'fr' );
564  $b->addLanguageLink( 'ru' );
565  $b->addInterwikiLink( Title::makeTitle( NS_MAIN, 'Kittens FR', '', 'fr' ) );
566  $b->addInterwikiLink( Title::makeTitle( NS_MAIN, 'Dragons RU', '', 'ru' ) );
567  $b->addExternalLink( 'https://dragons.wikimedia.test' );
568  $b->addExternalLink( 'https://goats.wikimedia.test' );
569 
570  $b->addCategory( 'Bar', 'Y' );
571  $b->addImage( 'Puff.jpg', '20180101000017', 'BEEF' );
572 
573  yield 'all kinds of links' => [ $a, $b, [
574  'getLinks' => [
575  NS_MAIN => [
576  'Kittens' => 6,
577  'Goats' => 7,
578  'Dragons' => 8,
579  ],
580  NS_TALK => [
581  'Kittens' => 16,
582  'Goats' => 17,
583  ],
584  NS_FILE => [
585  'Dragons.jpg' => 28,
586  ],
587  ],
588  'getTemplates' => [
589  NS_MAIN => [
590  'Dragons' => 118,
591  ],
592  NS_TEMPLATE => [
593  'Dragons' => 108,
594  'Goats' => 107,
595  ],
596  ],
597  'getTemplateIds' => [
598  NS_MAIN => [
599  'Dragons' => 1118,
600  ],
601  NS_TEMPLATE => [
602  'Dragons' => 1108,
603  'Goats' => 1107,
604  ],
605  ],
606  'getLanguageLinks' => [ 'de', 'ru', 'fr' ],
607  'getInterwikiLinks' => [
608  'de' => [ 'Kittens_DE' => 1 ],
609  'ru' => [ 'Kittens_RU' => 1, 'Dragons_RU' => 1, ],
610  'fr' => [ 'Kittens_FR' => 1 ],
611  ],
612  'getCategories' => [ 'Foo' => 'X', 'Bar' => 'Y' ],
613  'getImages' => [ 'Billy.jpg' => 1, 'Puff.jpg' => 1 ],
614  'getFileSearchOptions' => [
615  'Billy.jpg' => [ 'time' => '20180101000013', 'sha1' => 'DEAD' ],
616  'Puff.jpg' => [ 'time' => '20180101000017', 'sha1' => 'BEEF' ],
617  ],
618  'getExternalLinks' => [
619  'https://dragons.wikimedia.test' => 1,
620  'https://kittens.wikimedia.test' => 1,
621  'https://goats.wikimedia.test' => 1,
622  ]
623  ] ];
624 
625  // properties ------------
626  $a = new ParserOutput();
627 
628  $a->setProperty( 'foo', 'Foo!' );
629  $a->setProperty( 'bar', 'Bar!' );
630 
631  $a->setExtensionData( 'foo', 'Foo!' );
632  $a->setExtensionData( 'bar', 'Bar!' );
633 
634  $b = new ParserOutput();
635 
636  $b->setProperty( 'zoo', 'Zoo!' );
637  $b->setProperty( 'bar', 'Barrr!' );
638 
639  $b->setExtensionData( 'zoo', 'Zoo!' );
640  $b->setExtensionData( 'bar', 'Barrr!' );
641 
642  yield 'properties' => [ $a, $b, [
643  'getProperties' => [
644  'foo' => 'Foo!',
645  'bar' => 'Barrr!',
646  'zoo' => 'Zoo!',
647  ],
648  '$mExtensionData' => [
649  'foo' => 'Foo!',
650  'bar' => 'Barrr!',
651  'zoo' => 'Zoo!',
652  ],
653  ] ];
654  }
655 
664  public function testMergeTrackingMetaDataFrom( ParserOutput $a, ParserOutput $b, $expected ) {
665  $a->mergeTrackingMetaDataFrom( $b );
666 
667  $this->assertFieldValues( $a, $expected );
668 
669  // test twice, to make sure the operation is idempotent
670  $a->mergeTrackingMetaDataFrom( $b );
671 
672  $this->assertFieldValues( $a, $expected );
673  }
674 
676  // hooks
677  $a = new ParserOutput();
678 
679  $a->addOutputHook( 'foo', 'X' );
680  $a->addOutputHook( 'bar' );
681 
682  $b = new ParserOutput();
683 
684  $b->addOutputHook( 'foo', 'Y' );
685  $b->addOutputHook( 'bar' );
686  $b->addOutputHook( 'zoo' );
687 
688  yield 'hooks' => [ $a, $b, [
689  'getOutputHooks' => [
690  [ 'foo', 'X' ],
691  [ 'bar', false ],
692  [ 'foo', 'Y' ],
693  [ 'zoo', false ],
694  ],
695  ] ];
696 
697  // flags & co
698  $a = new ParserOutput();
699 
700  $a->addWarning( 'Oops' );
701  $a->addWarning( 'Whoops' );
702 
703  $a->setFlag( 'foo' );
704  $a->setFlag( 'bar' );
705 
706  $a->recordOption( 'Foo' );
707  $a->recordOption( 'Bar' );
708 
709  $b = new ParserOutput();
710 
711  $b->addWarning( 'Yikes' );
712  $b->addWarning( 'Whoops' );
713 
714  $b->setFlag( 'zoo' );
715  $b->setFlag( 'bar' );
716 
717  $b->recordOption( 'Zoo' );
718  $b->recordOption( 'Bar' );
719 
720  yield 'flags' => [ $a, $b, [
721  'getWarnings' => [ 'Oops', 'Whoops', 'Yikes' ],
722  '$mFlags' => [ 'foo' => true, 'bar' => true, 'zoo' => true ],
723  'getUsedOptions' => [ 'Foo', 'Bar', 'Zoo' ],
724  ] ];
725 
726  // timestamp ------------
727  $a = new ParserOutput();
728  $a->setTimestamp( '20180101000011' );
729  $b = new ParserOutput();
730  yield 'only left timestamp' => [ $a, $b, [ 'getTimestamp' => '20180101000011' ] ];
731 
732  $a = new ParserOutput();
733  $b = new ParserOutput();
734  $b->setTimestamp( '20180101000011' );
735  yield 'only right timestamp' => [ $a, $b, [ 'getTimestamp' => '20180101000011' ] ];
736 
737  $a = new ParserOutput();
738  $a->setTimestamp( '20180101000011' );
739  $b = new ParserOutput();
740  $b->setTimestamp( '20180101000001' );
741  yield 'left timestamp wins' => [ $a, $b, [ 'getTimestamp' => '20180101000011' ] ];
742 
743  $a = new ParserOutput();
744  $a->setTimestamp( '20180101000001' );
745  $b = new ParserOutput();
746  $b->setTimestamp( '20180101000011' );
747  yield 'right timestamp wins' => [ $a, $b, [ 'getTimestamp' => '20180101000011' ] ];
748 
749  // speculative rev id ------------
750  $a = new ParserOutput();
751  $a->setSpeculativeRevIdUsed( 9 );
752  $b = new ParserOutput();
753  yield 'only left speculative rev id' => [ $a, $b, [ 'getSpeculativeRevIdUsed' => 9 ] ];
754 
755  $a = new ParserOutput();
756  $b = new ParserOutput();
757  $b->setSpeculativeRevIdUsed( 9 );
758  yield 'only right speculative rev id' => [ $a, $b, [ 'getSpeculativeRevIdUsed' => 9 ] ];
759 
760  $a = new ParserOutput();
761  $a->setSpeculativeRevIdUsed( 9 );
762  $b = new ParserOutput();
763  $b->setSpeculativeRevIdUsed( 9 );
764  yield 'same speculative rev id' => [ $a, $b, [ 'getSpeculativeRevIdUsed' => 9 ] ];
765 
766  // limit report (recursive max) ------------
767  $a = new ParserOutput();
768 
769  $a->setLimitReportData( 'naive1', 7 );
770  $a->setLimitReportData( 'naive2', 27 );
771 
772  $a->setLimitReportData( 'limitreport-simple1', 7 );
773  $a->setLimitReportData( 'limitreport-simple2', 27 );
774 
775  $a->setLimitReportData( 'limitreport-pair1', [ 7, 9 ] );
776  $a->setLimitReportData( 'limitreport-pair2', [ 27, 29 ] );
777 
778  $a->setLimitReportData( 'limitreport-more1', [ 7, 9, 1 ] );
779  $a->setLimitReportData( 'limitreport-more2', [ 27, 29, 21 ] );
780 
781  $a->setLimitReportData( 'limitreport-only-a', 13 );
782 
783  $b = new ParserOutput();
784 
785  $b->setLimitReportData( 'naive1', 17 );
786  $b->setLimitReportData( 'naive2', 17 );
787 
788  $b->setLimitReportData( 'limitreport-simple1', 17 );
789  $b->setLimitReportData( 'limitreport-simple2', 17 );
790 
791  $b->setLimitReportData( 'limitreport-pair1', [ 17, 19 ] );
792  $b->setLimitReportData( 'limitreport-pair2', [ 17, 19 ] );
793 
794  $b->setLimitReportData( 'limitreport-more1', [ 17, 19, 11 ] );
795  $b->setLimitReportData( 'limitreport-more2', [ 17, 19, 11 ] );
796 
797  $b->setLimitReportData( 'limitreport-only-b', 23 );
798 
799  // first write wins
800  yield 'limit report' => [ $a, $b, [
801  'getLimitReportData' => [
802  'naive1' => 7,
803  'naive2' => 27,
804  'limitreport-simple1' => 7,
805  'limitreport-simple2' => 27,
806  'limitreport-pair1' => [ 7, 9 ],
807  'limitreport-pair2' => [ 27, 29 ],
808  'limitreport-more1' => [ 7, 9, 1 ],
809  'limitreport-more2' => [ 27, 29, 21 ],
810  'limitreport-only-a' => 13,
811  ],
812  'getLimitReportJSData' => [
813  'naive1' => 7,
814  'naive2' => 27,
815  'limitreport' => [
816  'simple1' => 7,
817  'simple2' => 27,
818  'pair1' => [ 'value' => 7, 'limit' => 9 ],
819  'pair2' => [ 'value' => 27, 'limit' => 29 ],
820  'more1' => [ 7, 9, 1 ],
821  'more2' => [ 27, 29, 21 ],
822  'only-a' => 13,
823  ],
824  ],
825  ] ];
826  }
827 
836  public function testMergeInternalMetaDataFrom( ParserOutput $a, ParserOutput $b, $expected ) {
837  $a->mergeInternalMetaDataFrom( $b );
838 
839  $this->assertFieldValues( $a, $expected );
840 
841  // test twice, to make sure the operation is idempotent
842  $a->mergeInternalMetaDataFrom( $b );
843 
844  $this->assertFieldValues( $a, $expected );
845  }
846 
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 );
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 );
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 );
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 }
and how to run hooks for an and one after Each event has a preferably in CamelCase For ArticleDelete hook A clump of code and data that should be run when an event happens This can be either a function and a chunk of data
Definition: hooks.txt:6
testGetRawText_failsIfNoText()
ParserOutput::getRawText.
const NS_MAIN
Definition: Defines.php:60
testExtensionData()
ParserOutput::setExtensionData ParserOutput::getExtensionData.
processing should stop and the error should be shown to the user * false
Definition: hooks.txt:187
per default it will return the text for text based content
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
Database ^— trigger DB shadowing because we are using Title magic.
$value
static isLinkInternal( $internal, $url)
Checks, if a url is pointing to the own server.
target page
const NS_TEMPLATE
Definition: Defines.php:70
see documentation in includes Linker php for Linker::makeImageLink & $time
Definition: hooks.txt:1780
title
This document provides an overview of the usage of PageUpdater and that is
Definition: pageupdater.txt:3
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:1971
testGetText( $options, $text, $expect)
ParserOutput::getText provideGetText.
testGetText_failsIfNoText()
ParserOutput::getText.
mergeTrackingMetaDataFrom(ParserOutput $source)
Merges dependency tracking metadata such as backlinks, images used, and extension data from $source i...
testGetCacheTime()
ParserOutput::getCacheTime ParserOutput::setCacheTime.
testMergeInternalMetaDataFrom(ParserOutput $a, ParserOutput $b, $expected)
provideMergeInternalMetaDataFrom ParserOutput::mergeInternalMetaDataFrom
testWrapperDivClass()
ParserOutput::getWrapperDivClass ParserOutput::addWrapperDivClass ParserOutput::clearWrapperDivClass ...
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:1971
shown</td >< td > a href
static provideIsLinkInternal()
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not null
Definition: hooks.txt:767
testMergeTrackingMetaDataFrom(ParserOutput $a, ParserOutput $b, $expected)
provideMergeTrackingMetaDataFrom ParserOutput::mergeTrackingMetaDataFrom
const NS_FILE
Definition: Defines.php:66
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
Definition: distributors.txt:9
testHasText()
ParserOutput::hasText.
assertFieldValues(ParserOutput $po, $expected)
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:590
testIsLinkInternal( $shouldMatch, $server, $url)
Test to make sure ParserOutput::isLinkInternal behaves properly provideIsLinkInternal ParserOutput::i...
mergeInternalMetaDataFrom(ParserOutput $source)
Merges internal metadata such as flags, accessed options, and profiling info from $source into this P...
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:35
testMergeHtmlMetaDataFrom(ParserOutput $a, ParserOutput $b, $expected)
provideMergeHtmlMetaDataFrom ParserOutput::mergeHtmlMetaDataFrom
you have access to all of the normal MediaWiki so you can get a DB use the etc For full docs on the Maintenance class
Definition: maintenance.txt:52
Bar style
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:3038
const NS_TALK
Definition: Defines.php:61
testMergeInternalMetaDataFrom_parseStartTime()
ParserOutput::mergeInternalMetaDataFrom ParserOutput::getTimes ParserOutput::resetParseStartTime.
testProperties()
ParserOutput::setProperty ParserOutput::getProperty ParserOutput::unsetProperty ParserOutput::getProp...
mergeHtmlMetaDataFrom(ParserOutput $source)
Merges HTML metadata such as head items, JS config vars, and HTTP cache control info from $source int...