MediaWiki  master
ParserOutputTest.php
Go to the documentation of this file.
1 <?php
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>
200 EOF;
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>
213 EOF;
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>
244 EOF
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>
274 EOF
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>
293 EOF
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>
307 EOF
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 
347  public function testGetRawText_failsIfNoText() {
348  $po = new ParserOutput( null );
349 
350  $this->setExpectedException( LogicException::class );
351  $po->getRawText();
352  }
353 
354  public function provideMergeHtmlMetaDataFrom() {
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->addModuleStyles( 'test-module-styles-a' );
401  $b->addJsConfigVars( 'test-config-var-a', 'a' );
402 
403  $b = new ParserOutput();
404  $b->setIndexPolicy( 'noindex' );
405  $b->addHeadItem( '<foo2>' );
406  $b->addHeadItem( '<bar2>', 'bar' );
407  $b->addModules( 'test-module-b' );
408  $b->addModuleStyles( 'test-module-styles-b' );
409  $b->addJsConfigVars( 'test-config-var-b', 'b' );
410  $b->addJsConfigVars( 'test-config-var-a', 'X' );
411 
412  yield 'head items and friends' => [ $a, $b, [
413  'getHeadItems' => [
414  '<foo1>',
415  '<foo2>',
416  'bar' => '<bar2>', // overwritten
417  ],
418  'getModules' => [
419  'test-module-a',
420  'test-module-b',
421  ],
422  'getModuleStyles' => [
423  'test-module-styles-a',
424  'test-module-styles-b',
425  ],
426  'getJsConfigVars' => [
427  'test-config-var-a' => 'X', // overwritten
428  'test-config-var-b' => 'b',
429  ],
430  ] ];
431 
432  // TOC ------------
433  $a = new ParserOutput();
434  $a->setTOCHTML( '<p>TOC A</p>' );
435  $a->setSections( [ [ 'fromtitle' => 'A1' ], [ 'fromtitle' => 'A2' ] ] );
436 
437  $b = new ParserOutput();
438  $b->setTOCHTML( '<p>TOC B</p>' );
439  $b->setSections( [ [ 'fromtitle' => 'B1' ], [ 'fromtitle' => 'B2' ] ] );
440 
441  yield 'concat TOC' => [ $a, $b, [
442  'getTOCHTML' => '<p>TOC A</p><p>TOC B</p>',
443  'getSections' => [
444  [ 'fromtitle' => 'A1' ],
445  [ 'fromtitle' => 'A2' ],
446  [ 'fromtitle' => 'B1' ],
447  [ 'fromtitle' => 'B2' ]
448  ],
449  ] ];
450 
451  // Skin Control ------------
452  $a = new ParserOutput();
453  $a->setNewSection( true );
454  $a->hideNewSection( true );
455  $a->setNoGallery( true );
456  $a->addWrapperDivClass( 'foo' );
457 
458  $a->setIndicator( 'foo', 'Foo!' );
459  $a->setIndicator( 'bar', 'Bar!' );
460 
461  $a->setExtensionData( 'foo', 'Foo!' );
462  $a->setExtensionData( 'bar', 'Bar!' );
463 
464  $b = new ParserOutput();
465  $b->setNoGallery( true );
466  $b->setEnableOOUI( true );
467  $b->preventClickjacking( true );
468  $a->addWrapperDivClass( 'bar' );
469 
470  $b->setIndicator( 'zoo', 'Zoo!' );
471  $b->setIndicator( 'bar', 'Barrr!' );
472 
473  $b->setExtensionData( 'zoo', 'Zoo!' );
474  $b->setExtensionData( 'bar', 'Barrr!' );
475 
476  yield 'skin control flags' => [ $a, $b, [
477  'getNewSection' => true,
478  'getHideNewSection' => true,
479  'getNoGallery' => true,
480  'getEnableOOUI' => true,
481  'preventClickjacking' => true,
482  'getIndicators' => [
483  'foo' => 'Foo!',
484  'bar' => 'Barrr!',
485  'zoo' => 'Zoo!',
486  ],
487  'getWrapperDivClass' => 'foo bar',
488  '$mExtensionData' => [
489  'foo' => 'Foo!',
490  'bar' => 'Barrr!',
491  'zoo' => 'Zoo!',
492  ],
493  ] ];
494  }
495 
504  public function testMergeHtmlMetaDataFrom( ParserOutput $a, ParserOutput $b, $expected ) {
505  $a->mergeHtmlMetaDataFrom( $b );
506 
507  $this->assertFieldValues( $a, $expected );
508 
509  // test twice, to make sure the operation is idempotent (except for the TOC, see below)
510  $a->mergeHtmlMetaDataFrom( $b );
511 
512  // XXX: TOC joining should get smarter. Can we make it idempotent as well?
513  unset( $expected['getTOCHTML'] );
514  unset( $expected['getSections'] );
515 
516  $this->assertFieldValues( $a, $expected );
517  }
518 
519  private function assertFieldValues( ParserOutput $po, $expected ) {
520  $po = TestingAccessWrapper::newFromObject( $po );
521 
522  foreach ( $expected as $method => $value ) {
523  if ( $method[0] === '$' ) {
524  $field = substr( $method, 1 );
525  $actual = $po->__get( $field );
526  } else {
527  $actual = $po->__call( $method, [] );
528  }
529 
530  $this->assertEquals( $value, $actual, $method );
531  }
532  }
533 
535  // links ------------
536  $a = new ParserOutput();
537  $a->addLink( Title::makeTitle( NS_MAIN, 'Kittens' ), 6 );
538  $a->addLink( Title::makeTitle( NS_TALK, 'Kittens' ), 16 );
539  $a->addLink( Title::makeTitle( NS_MAIN, 'Goats' ), 7 );
540 
541  $a->addTemplate( Title::makeTitle( NS_TEMPLATE, 'Goats' ), 107, 1107 );
542 
543  $a->addLanguageLink( 'de' );
544  $a->addLanguageLink( 'ru' );
545  $a->addInterwikiLink( Title::makeTitle( NS_MAIN, 'Kittens DE', '', 'de' ) );
546  $a->addInterwikiLink( Title::makeTitle( NS_MAIN, 'Kittens RU', '', 'ru' ) );
547  $a->addExternalLink( 'https://kittens.wikimedia.test' );
548  $a->addExternalLink( 'https://goats.wikimedia.test' );
549 
550  $a->addCategory( 'Foo', 'X' );
551  $a->addImage( 'Billy.jpg', '20180101000013', 'DEAD' );
552 
553  $b = new ParserOutput();
554  $b->addLink( Title::makeTitle( NS_MAIN, 'Goats' ), 7 );
555  $b->addLink( Title::makeTitle( NS_TALK, 'Goats' ), 17 );
556  $b->addLink( Title::makeTitle( NS_MAIN, 'Dragons' ), 8 );
557  $b->addLink( Title::makeTitle( NS_FILE, 'Dragons.jpg' ), 28 );
558 
559  $b->addTemplate( Title::makeTitle( NS_TEMPLATE, 'Dragons' ), 108, 1108 );
560  $a->addTemplate( Title::makeTitle( NS_MAIN, 'Dragons' ), 118, 1118 );
561 
562  $b->addLanguageLink( 'fr' );
563  $b->addLanguageLink( 'ru' );
564  $b->addInterwikiLink( Title::makeTitle( NS_MAIN, 'Kittens FR', '', 'fr' ) );
565  $b->addInterwikiLink( Title::makeTitle( NS_MAIN, 'Dragons RU', '', 'ru' ) );
566  $b->addExternalLink( 'https://dragons.wikimedia.test' );
567  $b->addExternalLink( 'https://goats.wikimedia.test' );
568 
569  $b->addCategory( 'Bar', 'Y' );
570  $b->addImage( 'Puff.jpg', '20180101000017', 'BEEF' );
571 
572  yield 'all kinds of links' => [ $a, $b, [
573  'getLinks' => [
574  NS_MAIN => [
575  'Kittens' => 6,
576  'Goats' => 7,
577  'Dragons' => 8,
578  ],
579  NS_TALK => [
580  'Kittens' => 16,
581  'Goats' => 17,
582  ],
583  NS_FILE => [
584  'Dragons.jpg' => 28,
585  ],
586  ],
587  'getTemplates' => [
588  NS_MAIN => [
589  'Dragons' => 118,
590  ],
591  NS_TEMPLATE => [
592  'Dragons' => 108,
593  'Goats' => 107,
594  ],
595  ],
596  'getTemplateIds' => [
597  NS_MAIN => [
598  'Dragons' => 1118,
599  ],
600  NS_TEMPLATE => [
601  'Dragons' => 1108,
602  'Goats' => 1107,
603  ],
604  ],
605  'getLanguageLinks' => [ 'de', 'ru', 'fr' ],
606  'getInterwikiLinks' => [
607  'de' => [ 'Kittens_DE' => 1 ],
608  'ru' => [ 'Kittens_RU' => 1, 'Dragons_RU' => 1, ],
609  'fr' => [ 'Kittens_FR' => 1 ],
610  ],
611  'getCategories' => [ 'Foo' => 'X', 'Bar' => 'Y' ],
612  'getImages' => [ 'Billy.jpg' => 1, 'Puff.jpg' => 1 ],
613  'getFileSearchOptions' => [
614  'Billy.jpg' => [ 'time' => '20180101000013', 'sha1' => 'DEAD' ],
615  'Puff.jpg' => [ 'time' => '20180101000017', 'sha1' => 'BEEF' ],
616  ],
617  'getExternalLinks' => [
618  'https://dragons.wikimedia.test' => 1,
619  'https://kittens.wikimedia.test' => 1,
620  'https://goats.wikimedia.test' => 1,
621  ]
622  ] ];
623 
624  // properties ------------
625  $a = new ParserOutput();
626 
627  $a->setProperty( 'foo', 'Foo!' );
628  $a->setProperty( 'bar', 'Bar!' );
629 
630  $a->setExtensionData( 'foo', 'Foo!' );
631  $a->setExtensionData( 'bar', 'Bar!' );
632 
633  $b = new ParserOutput();
634 
635  $b->setProperty( 'zoo', 'Zoo!' );
636  $b->setProperty( 'bar', 'Barrr!' );
637 
638  $b->setExtensionData( 'zoo', 'Zoo!' );
639  $b->setExtensionData( 'bar', 'Barrr!' );
640 
641  yield 'properties' => [ $a, $b, [
642  'getProperties' => [
643  'foo' => 'Foo!',
644  'bar' => 'Barrr!',
645  'zoo' => 'Zoo!',
646  ],
647  '$mExtensionData' => [
648  'foo' => 'Foo!',
649  'bar' => 'Barrr!',
650  'zoo' => 'Zoo!',
651  ],
652  ] ];
653  }
654 
663  public function testMergeTrackingMetaDataFrom( ParserOutput $a, ParserOutput $b, $expected ) {
664  $a->mergeTrackingMetaDataFrom( $b );
665 
666  $this->assertFieldValues( $a, $expected );
667 
668  // test twice, to make sure the operation is idempotent
669  $a->mergeTrackingMetaDataFrom( $b );
670 
671  $this->assertFieldValues( $a, $expected );
672  }
673 
675  // hooks
676  $a = new ParserOutput();
677 
678  $a->addOutputHook( 'foo', 'X' );
679  $a->addOutputHook( 'bar' );
680 
681  $b = new ParserOutput();
682 
683  $b->addOutputHook( 'foo', 'Y' );
684  $b->addOutputHook( 'bar' );
685  $b->addOutputHook( 'zoo' );
686 
687  yield 'hooks' => [ $a, $b, [
688  'getOutputHooks' => [
689  [ 'foo', 'X' ],
690  [ 'bar', false ],
691  [ 'foo', 'Y' ],
692  [ 'zoo', false ],
693  ],
694  ] ];
695 
696  // flags & co
697  $a = new ParserOutput();
698 
699  $a->addWarning( 'Oops' );
700  $a->addWarning( 'Whoops' );
701 
702  $a->setFlag( 'foo' );
703  $a->setFlag( 'bar' );
704 
705  $a->recordOption( 'Foo' );
706  $a->recordOption( 'Bar' );
707 
708  $b = new ParserOutput();
709 
710  $b->addWarning( 'Yikes' );
711  $b->addWarning( 'Whoops' );
712 
713  $b->setFlag( 'zoo' );
714  $b->setFlag( 'bar' );
715 
716  $b->recordOption( 'Zoo' );
717  $b->recordOption( 'Bar' );
718 
719  yield 'flags' => [ $a, $b, [
720  'getWarnings' => [ 'Oops', 'Whoops', 'Yikes' ],
721  '$mFlags' => [ 'foo' => true, 'bar' => true, 'zoo' => true ],
722  'getUsedOptions' => [ 'Foo', 'Bar', 'Zoo' ],
723  ] ];
724 
725  // timestamp ------------
726  $a = new ParserOutput();
727  $a->setTimestamp( '20180101000011' );
728  $b = new ParserOutput();
729  yield 'only left timestamp' => [ $a, $b, [ 'getTimestamp' => '20180101000011' ] ];
730 
731  $a = new ParserOutput();
732  $b = new ParserOutput();
733  $b->setTimestamp( '20180101000011' );
734  yield 'only right timestamp' => [ $a, $b, [ 'getTimestamp' => '20180101000011' ] ];
735 
736  $a = new ParserOutput();
737  $a->setTimestamp( '20180101000011' );
738  $b = new ParserOutput();
739  $b->setTimestamp( '20180101000001' );
740  yield 'left timestamp wins' => [ $a, $b, [ 'getTimestamp' => '20180101000011' ] ];
741 
742  $a = new ParserOutput();
743  $a->setTimestamp( '20180101000001' );
744  $b = new ParserOutput();
745  $b->setTimestamp( '20180101000011' );
746  yield 'right timestamp wins' => [ $a, $b, [ 'getTimestamp' => '20180101000011' ] ];
747 
748  // speculative rev id ------------
749  $a = new ParserOutput();
750  $a->setSpeculativeRevIdUsed( 9 );
751  $b = new ParserOutput();
752  yield 'only left speculative rev id' => [ $a, $b, [ 'getSpeculativeRevIdUsed' => 9 ] ];
753 
754  $a = new ParserOutput();
755  $b = new ParserOutput();
756  $b->setSpeculativeRevIdUsed( 9 );
757  yield 'only right speculative rev id' => [ $a, $b, [ 'getSpeculativeRevIdUsed' => 9 ] ];
758 
759  $a = new ParserOutput();
760  $a->setSpeculativeRevIdUsed( 9 );
761  $b = new ParserOutput();
762  $b->setSpeculativeRevIdUsed( 9 );
763  yield 'same speculative rev id' => [ $a, $b, [ 'getSpeculativeRevIdUsed' => 9 ] ];
764 
765  // limit report (recursive max) ------------
766  $a = new ParserOutput();
767 
768  $a->setLimitReportData( 'naive1', 7 );
769  $a->setLimitReportData( 'naive2', 27 );
770 
771  $a->setLimitReportData( 'limitreport-simple1', 7 );
772  $a->setLimitReportData( 'limitreport-simple2', 27 );
773 
774  $a->setLimitReportData( 'limitreport-pair1', [ 7, 9 ] );
775  $a->setLimitReportData( 'limitreport-pair2', [ 27, 29 ] );
776 
777  $a->setLimitReportData( 'limitreport-more1', [ 7, 9, 1 ] );
778  $a->setLimitReportData( 'limitreport-more2', [ 27, 29, 21 ] );
779 
780  $a->setLimitReportData( 'limitreport-only-a', 13 );
781 
782  $b = new ParserOutput();
783 
784  $b->setLimitReportData( 'naive1', 17 );
785  $b->setLimitReportData( 'naive2', 17 );
786 
787  $b->setLimitReportData( 'limitreport-simple1', 17 );
788  $b->setLimitReportData( 'limitreport-simple2', 17 );
789 
790  $b->setLimitReportData( 'limitreport-pair1', [ 17, 19 ] );
791  $b->setLimitReportData( 'limitreport-pair2', [ 17, 19 ] );
792 
793  $b->setLimitReportData( 'limitreport-more1', [ 17, 19, 11 ] );
794  $b->setLimitReportData( 'limitreport-more2', [ 17, 19, 11 ] );
795 
796  $b->setLimitReportData( 'limitreport-only-b', 23 );
797 
798  // first write wins
799  yield 'limit report' => [ $a, $b, [
800  'getLimitReportData' => [
801  'naive1' => 7,
802  'naive2' => 27,
803  'limitreport-simple1' => 7,
804  'limitreport-simple2' => 27,
805  'limitreport-pair1' => [ 7, 9 ],
806  'limitreport-pair2' => [ 27, 29 ],
807  'limitreport-more1' => [ 7, 9, 1 ],
808  'limitreport-more2' => [ 27, 29, 21 ],
809  'limitreport-only-a' => 13,
810  ],
811  'getLimitReportJSData' => [
812  'naive1' => 7,
813  'naive2' => 27,
814  'limitreport' => [
815  'simple1' => 7,
816  'simple2' => 27,
817  'pair1' => [ 'value' => 7, 'limit' => 9 ],
818  'pair2' => [ 'value' => 27, 'limit' => 29 ],
819  'more1' => [ 7, 9, 1 ],
820  'more2' => [ 27, 29, 21 ],
821  'only-a' => 13,
822  ],
823  ],
824  ] ];
825  }
826 
835  public function testMergeInternalMetaDataFrom( ParserOutput $a, ParserOutput $b, $expected ) {
836  $a->mergeInternalMetaDataFrom( $b );
837 
838  $this->assertFieldValues( $a, $expected );
839 
840  // test twice, to make sure the operation is idempotent
841  $a->mergeInternalMetaDataFrom( $b );
842 
843  $this->assertFieldValues( $a, $expected );
844  }
845 
853  $a = new ParserOutput();
854  $a = TestingAccessWrapper::newFromObject( $a );
855 
856  $a->resetParseStartTime();
857  $aClocks = $a->mParseStartTime;
858 
859  $b = new ParserOutput();
860 
861  $a->mergeInternalMetaDataFrom( $b );
862  $mergedClocks = $a->mParseStartTime;
863 
864  foreach ( $mergedClocks as $clock => $timestamp ) {
865  $this->assertSame( $aClocks[$clock], $timestamp, $clock );
866  }
867 
868  // try again, with times in $b also set, and later than $a's
869  usleep( 1234 );
870 
872  $b = new ParserOutput();
873  $b = TestingAccessWrapper::newFromObject( $b );
874 
875  $b->resetParseStartTime();
876 
877  $bClocks = $b->mParseStartTime;
878 
879  $a->mergeInternalMetaDataFrom( $b->object, 'b' );
880  $mergedClocks = $a->mParseStartTime;
881 
882  foreach ( $mergedClocks as $clock => $timestamp ) {
883  $this->assertSame( $aClocks[$clock], $timestamp, $clock );
884  $this->assertLessThanOrEqual( $bClocks[$clock], $timestamp, $clock );
885  }
886 
887  // try again, with $a's times being later
888  usleep( 1234 );
889  $a->resetParseStartTime();
890  $aClocks = $a->mParseStartTime;
891 
892  $a->mergeInternalMetaDataFrom( $b->object, 'b' );
893  $mergedClocks = $a->mParseStartTime;
894 
895  foreach ( $mergedClocks as $clock => $timestamp ) {
896  $this->assertSame( $bClocks[$clock], $timestamp, $clock );
897  $this->assertLessThanOrEqual( $aClocks[$clock], $timestamp, $clock );
898  }
899 
900  // try again, with no times in $a set
901  $a = new ParserOutput();
902  $a = TestingAccessWrapper::newFromObject( $a );
903 
904  $a->mergeInternalMetaDataFrom( $b->object, 'b' );
905  $mergedClocks = $a->mParseStartTime;
906 
907  foreach ( $mergedClocks as $clock => $timestamp ) {
908  $this->assertSame( $bClocks[$clock], $timestamp, $clock );
909  }
910  }
911 
916  public function testGetCacheTime() {
917  $clock = MWTimestamp::convert( TS_UNIX, '20100101000000' );
918  MWTimestamp::setFakeTime( function () use ( &$clock ) {
919  return $clock++;
920  } );
921 
922  $po = new ParserOutput();
923  $time = $po->getCacheTime();
924 
925  // Use current (fake) time per default. Ignore the last digit.
926  // Subsequent calls must yield the exact same timestamp as the first.
927  $this->assertStringStartsWith( '2010010100000', $time );
928  $this->assertSame( $time, $po->getCacheTime() );
929 
930  // After setting, the getter must return the time that was set.
931  $time = '20110606112233';
932  $po->setCacheTime( $time );
933  $this->assertSame( $time, $po->getCacheTime() );
934 
935  // support -1 as a marker for "not cacheable"
936  $time = -1;
937  $po->setCacheTime( $time );
938  $this->assertSame( $time, $po->getCacheTime() );
939  }
940 
941 }
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:64
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:74
see documentation in includes Linker php for Linker::makeImageLink & $time
Definition: hooks.txt:1799
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:1982
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:1982
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:780
testMergeTrackingMetaDataFrom(ParserOutput $a, ParserOutput $b, $expected)
provideMergeTrackingMetaDataFrom ParserOutput::mergeTrackingMetaDataFrom
const NS_FILE
Definition: Defines.php:70
setMwGlobals( $pairs, $value=null)
Sets a global, maintaining a stashed version of the previous global to be restored in tearDown...
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:589
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:3050
const NS_TALK
Definition: Defines.php:65
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...