Merge "Add part to update ctd_user_defined in populateChangeTagDef"
[lhc/web/wiklou.git] / tests / phpunit / includes / page / ArticleViewTest.php
1 <?php
2 use MediaWiki\MediaWikiServices;
3 use MediaWiki\Storage\MutableRevisionRecord;
4 use MediaWiki\Storage\RevisionRecord;
5 use PHPUnit\Framework\MockObject\MockObject;
6
7 /**
8 * @covers \Article::view()
9 */
10 class ArticleViewTest extends MediaWikiTestCase {
11
12 protected function setUp() {
13 parent::setUp();
14
15 $this->setUserLang( 'qqx' );
16 }
17
18 private function getHtml( OutputPage $output ) {
19 return preg_replace( '/<!--.*?-->/s', '', $output->getHTML() );
20 }
21
22 /**
23 * @param string|Title $title
24 * @param Content[]|string[] $revisionContents Content of the revisions to create
25 * (as Content or string).
26 * @param RevisionRecord[] &$revisions will be filled with the RevisionRecord for $content.
27 *
28 * @return WikiPage
29 * @throws MWException
30 */
31 private function getPage( $title, array $revisionContents = [], array &$revisions = [] ) {
32 if ( is_string( $title ) ) {
33 $title = Title::makeTitle( $this->getDefaultWikitextNS(), $title );
34 }
35
36 $page = WikiPage::factory( $title );
37
38 $user = $this->getTestUser()->getUser();
39
40 foreach ( $revisionContents as $key => $cont ) {
41 if ( is_string( $cont ) ) {
42 $cont = new WikitextContent( $cont );
43 }
44
45 $u = $page->newPageUpdater( $user );
46 $u->setContent( 'main', $cont );
47 $rev = $u->saveRevision( CommentStoreComment::newUnsavedComment( 'Rev ' . $key ) );
48
49 $revisions[ $key ] = $rev;
50 }
51
52 return $page;
53 }
54
55 /**
56 * @covers Article::getOldId()
57 * @covers Article::getRevIdFetched()
58 */
59 public function testGetOldId() {
60 $revisions = [];
61 $page = $this->getPage( __METHOD__, [ 1 => 'Test A', 2 => 'Test B' ], $revisions );
62
63 $idA = $revisions[1]->getId();
64 $idB = $revisions[2]->getId();
65
66 // oldid in constructor
67 $article = new Article( $page->getTitle(), $idA );
68 $this->assertSame( $idA, $article->getOldID() );
69 $article->getRevisionFetched();
70 $this->assertSame( $idA, $article->getRevIdFetched() );
71
72 // oldid 0 in constructor
73 $article = new Article( $page->getTitle(), 0 );
74 $this->assertSame( 0, $article->getOldID() );
75 $article->getRevisionFetched();
76 $this->assertSame( $idB, $article->getRevIdFetched() );
77
78 // oldid in request
79 $article = new Article( $page->getTitle() );
80 $context = new RequestContext();
81 $context->setRequest( new FauxRequest( [ 'oldid' => $idA ] ) );
82 $article->setContext( $context );
83 $this->assertSame( $idA, $article->getOldID() );
84 $article->getRevisionFetched();
85 $this->assertSame( $idA, $article->getRevIdFetched() );
86
87 // no oldid
88 $article = new Article( $page->getTitle() );
89 $context = new RequestContext();
90 $context->setRequest( new FauxRequest( [] ) );
91 $article->setContext( $context );
92 $this->assertSame( 0, $article->getOldID() );
93 $article->getRevisionFetched();
94 $this->assertSame( $idB, $article->getRevIdFetched() );
95 }
96
97 public function testView() {
98 $page = $this->getPage( __METHOD__, [ 1 => 'Test A', 2 => 'Test B' ] );
99
100 $article = new Article( $page->getTitle(), 0 );
101 $article->getContext()->getOutput()->setTitle( $page->getTitle() );
102 $article->view();
103
104 $output = $article->getContext()->getOutput();
105 $this->assertContains( 'Test B', $this->getHtml( $output ) );
106 $this->assertNotContains( 'id="mw-revision-info"', $this->getHtml( $output ) );
107 $this->assertNotContains( 'id="mw-revision-nav"', $this->getHtml( $output ) );
108 }
109
110 public function testViewCached() {
111 $page = $this->getPage( __METHOD__, [ 1 => 'Test A', 2 => 'Test B' ] );
112
113 $po = new ParserOutput( 'Cached Text' );
114
115 $article = new Article( $page->getTitle(), 0 );
116 $article->getContext()->getOutput()->setTitle( $page->getTitle() );
117
118 $cache = MediaWikiServices::getInstance()->getParserCache();
119 $cache->save( $po, $page, $article->getParserOptions() );
120
121 $article->view();
122
123 $output = $article->getContext()->getOutput();
124 $this->assertContains( 'Cached Text', $this->getHtml( $output ) );
125 $this->assertNotContains( 'Test A', $this->getHtml( $output ) );
126 $this->assertNotContains( 'Test B', $this->getHtml( $output ) );
127 }
128
129 /**
130 * @covers Article::getRedirectTarget()
131 */
132 public function testViewRedirect() {
133 $target = Title::makeTitle( $this->getDefaultWikitextNS(), 'Test_Target' );
134 $redirectText = '#REDIRECT [[' . $target->getPrefixedText() . ']]';
135
136 $page = $this->getPage( __METHOD__, [ $redirectText ] );
137
138 $article = new Article( $page->getTitle(), 0 );
139 $article->getContext()->getOutput()->setTitle( $page->getTitle() );
140 $article->view();
141
142 $this->assertNotNull(
143 $article->getRedirectTarget()->getPrefixedDBkey()
144 );
145 $this->assertSame(
146 $target->getPrefixedDBkey(),
147 $article->getRedirectTarget()->getPrefixedDBkey()
148 );
149
150 $output = $article->getContext()->getOutput();
151 $this->assertContains( 'class="redirectText"', $this->getHtml( $output ) );
152 $this->assertContains(
153 '>' . htmlspecialchars( $target->getPrefixedText() ) . '<',
154 $this->getHtml( $output )
155 );
156 }
157
158 public function testViewNonText() {
159 $dummy = $this->getPage( __METHOD__, [ 'Dummy' ] );
160 $dummyRev = $dummy->getRevision()->getRevisionRecord();
161 $title = $dummy->getTitle();
162
163 /** @var MockObject|ContentHandler $mockHandler */
164 $mockHandler = $this->getMockBuilder( ContentHandler::class )
165 ->setMethods(
166 [
167 'isParserCacheSupported',
168 'serializeContent',
169 'unserializeContent',
170 'makeEmptyContent',
171 ]
172 )
173 ->setConstructorArgs( [ 'NotText', [ 'application/frobnitz' ] ] )
174 ->getMock();
175
176 $mockHandler->method( 'isParserCacheSupported' )
177 ->willReturn( false );
178
179 $this->setTemporaryHook(
180 'ContentHandlerForModelID',
181 function ( $id, &$handler ) use ( $mockHandler ) {
182 $handler = $mockHandler;
183 }
184 );
185
186 /** @var MockObject|Content $content */
187 $content = $this->getMock( Content::class );
188 $content->method( 'getParserOutput' )
189 ->willReturn( new ParserOutput( 'Structured Output' ) );
190 $content->method( 'getModel' )
191 ->willReturn( 'NotText' );
192 $content->method( 'getNativeData' )
193 ->willReturn( [ (object)[ 'x' => 'stuff' ] ] );
194 $content->method( 'copy' )
195 ->willReturn( $content );
196
197 $rev = new MutableRevisionRecord( $title );
198 $rev->setId( $dummyRev->getId() );
199 $rev->setPageId( $title->getArticleID() );
200 $rev->setUser( $dummyRev->getUser() );
201 $rev->setComment( $dummyRev->getComment() );
202 $rev->setTimestamp( $dummyRev->getTimestamp() );
203
204 $rev->setContent( 'main', $content );
205
206 $rev = new Revision( $rev );
207
208 /** @var MockObject|WikiPage $page */
209 $page = $this->getMockBuilder( WikiPage::class )
210 ->setMethods( [ 'getRevision', 'getLatest' ] )
211 ->setConstructorArgs( [ $title ] )
212 ->getMock();
213
214 $page->method( 'getRevision' )
215 ->willReturn( $rev );
216 $page->method( 'getLatest' )
217 ->willReturn( $rev->getId() );
218
219 $article = Article::newFromWikiPage( $page, RequestContext::getMain() );
220 $article->getContext()->getOutput()->setTitle( $page->getTitle() );
221 $article->view();
222
223 $output = $article->getContext()->getOutput();
224 $this->assertContains( 'Structured Output', $this->getHtml( $output ) );
225 $this->assertNotContains( 'Dummy', $this->getHtml( $output ) );
226 }
227
228 public function testViewOfOldRevision() {
229 $revisions = [];
230 $page = $this->getPage( __METHOD__, [ 1 => 'Test A', 2 => 'Test B' ], $revisions );
231 $idA = $revisions[1]->getId();
232
233 $article = new Article( $page->getTitle(), $idA );
234 $article->getContext()->getOutput()->setTitle( $page->getTitle() );
235 $article->view();
236
237 $output = $article->getContext()->getOutput();
238 $this->assertContains( 'Test A', $this->getHtml( $output ) );
239 $this->assertContains( 'id="mw-revision-info"', $output->getSubtitle() );
240 $this->assertContains( 'id="mw-revision-nav"', $output->getSubtitle() );
241
242 $this->assertNotContains( 'id="revision-info-current"', $output->getSubtitle() );
243 $this->assertNotContains( 'Test B', $this->getHtml( $output ) );
244 }
245
246 public function testViewOfCurrentRevision() {
247 $revisions = [];
248 $page = $this->getPage( __METHOD__, [ 1 => 'Test A', 2 => 'Test B' ], $revisions );
249 $idB = $revisions[2]->getId();
250
251 $article = new Article( $page->getTitle(), $idB );
252 $article->getContext()->getOutput()->setTitle( $page->getTitle() );
253 $article->view();
254
255 $output = $article->getContext()->getOutput();
256 $this->assertContains( 'Test B', $this->getHtml( $output ) );
257 $this->assertContains( 'id="mw-revision-info-current"', $output->getSubtitle() );
258 $this->assertContains( 'id="mw-revision-nav"', $output->getSubtitle() );
259 }
260
261 public function testViewOfMissingRevision() {
262 $revisions = [];
263 $page = $this->getPage( __METHOD__, [ 1 => 'Test A' ], $revisions );
264 $badId = $revisions[1]->getId() + 100;
265
266 $article = new Article( $page->getTitle(), $badId );
267 $article->getContext()->getOutput()->setTitle( $page->getTitle() );
268 $article->view();
269
270 $output = $article->getContext()->getOutput();
271 $this->assertContains( 'missing-revision: ' . $badId, $this->getHtml( $output ) );
272
273 $this->assertNotContains( 'Test A', $this->getHtml( $output ) );
274 }
275
276 public function testViewOfDeletedRevision() {
277 $revisions = [];
278 $page = $this->getPage( __METHOD__, [ 1 => 'Test A', 2 => 'Test B' ], $revisions );
279 $idA = $revisions[1]->getId();
280
281 $revDelList = new RevDelRevisionList(
282 RequestContext::getMain(), $page->getTitle(), [ $idA ]
283 );
284 $revDelList->setVisibility( [
285 'value' => [ RevisionRecord::DELETED_TEXT => 1 ],
286 'comment' => "Testing",
287 ] );
288
289 $article = new Article( $page->getTitle(), $idA );
290 $article->getContext()->getOutput()->setTitle( $page->getTitle() );
291 $article->view();
292
293 $output = $article->getContext()->getOutput();
294 $this->assertContains( '(rev-deleted-text-permission)', $this->getHtml( $output ) );
295
296 $this->assertNotContains( 'Test A', $this->getHtml( $output ) );
297 $this->assertNotContains( 'Test B', $this->getHtml( $output ) );
298 }
299
300 public function testViewMissingPage() {
301 $page = $this->getPage( __METHOD__ );
302
303 $article = new Article( $page->getTitle() );
304 $article->getContext()->getOutput()->setTitle( $page->getTitle() );
305 $article->view();
306
307 $output = $article->getContext()->getOutput();
308 $this->assertContains( '(noarticletextanon)', $this->getHtml( $output ) );
309 }
310
311 public function testViewDeletedPage() {
312 $page = $this->getPage( __METHOD__, [ 1 => 'Test A', 2 => 'Test B' ] );
313 $page->doDeleteArticle( 'Test' );
314
315 $article = new Article( $page->getTitle() );
316 $article->getContext()->getOutput()->setTitle( $page->getTitle() );
317 $article->view();
318
319 $output = $article->getContext()->getOutput();
320 $this->assertContains( 'moveddeleted', $this->getHtml( $output ) );
321 $this->assertContains( 'logentry-delete-delete', $this->getHtml( $output ) );
322 $this->assertContains( '(noarticletextanon)', $this->getHtml( $output ) );
323
324 $this->assertNotContains( 'Test A', $this->getHtml( $output ) );
325 $this->assertNotContains( 'Test B', $this->getHtml( $output ) );
326 }
327
328 public function testViewMessagePage() {
329 $title = Title::makeTitle( NS_MEDIAWIKI, 'Mainpage' );
330 $page = $this->getPage( $title );
331
332 $article = new Article( $page->getTitle() );
333 $article->getContext()->getOutput()->setTitle( $page->getTitle() );
334 $article->view();
335
336 $output = $article->getContext()->getOutput();
337 $this->assertContains(
338 wfMessage( 'mainpage' )->inContentLanguage()->parse(),
339 $this->getHtml( $output )
340 );
341 $this->assertNotContains( '(noarticletextanon)', $this->getHtml( $output ) );
342 }
343
344 public function testViewMissingUserPage() {
345 $user = $this->getTestUser()->getUser();
346 $user->addToDatabase();
347
348 $title = Title::makeTitle( NS_USER, $user->getName() );
349
350 $page = $this->getPage( $title );
351
352 $article = new Article( $page->getTitle() );
353 $article->getContext()->getOutput()->setTitle( $page->getTitle() );
354 $article->view();
355
356 $output = $article->getContext()->getOutput();
357 $this->assertContains( '(noarticletextanon)', $this->getHtml( $output ) );
358 $this->assertNotContains( '(userpage-userdoesnotexist-view)', $this->getHtml( $output ) );
359 }
360
361 public function testViewUserPageOfNonexistingUser() {
362 $user = User::newFromName( 'Testing ' . __METHOD__ );
363
364 $title = Title::makeTitle( NS_USER, $user->getName() );
365
366 $page = $this->getPage( $title );
367
368 $article = new Article( $page->getTitle() );
369 $article->getContext()->getOutput()->setTitle( $page->getTitle() );
370 $article->view();
371
372 $output = $article->getContext()->getOutput();
373 $this->assertContains( '(noarticletextanon)', $this->getHtml( $output ) );
374 $this->assertContains( '(userpage-userdoesnotexist-view:', $this->getHtml( $output ) );
375 }
376
377 public function testArticleViewHeaderHook() {
378 $page = $this->getPage( __METHOD__, [ 1 => 'Test A' ] );
379
380 $article = new Article( $page->getTitle(), 0 );
381 $article->getContext()->getOutput()->setTitle( $page->getTitle() );
382
383 $this->setTemporaryHook(
384 'ArticleViewHeader',
385 function ( Article $articlePage, &$outputDone, &$useParserCache ) use ( $article ) {
386 $this->assertSame( $article, $articlePage, '$articlePage' );
387
388 $outputDone = new ParserOutput( 'Hook Text' );
389 $outputDone->setTitleText( 'Hook Title' );
390
391 $articlePage->getContext()->getOutput()->addParserOutput( $outputDone );
392 }
393 );
394
395 $article->view();
396
397 $output = $article->getContext()->getOutput();
398 $this->assertNotContains( 'Test A', $this->getHtml( $output ) );
399 $this->assertContains( 'Hook Text', $this->getHtml( $output ) );
400 $this->assertSame( 'Hook Title', $output->getPageTitle() );
401 }
402
403 public function testArticleContentViewCustomHook() {
404 $page = $this->getPage( __METHOD__, [ 1 => 'Test A' ] );
405
406 $article = new Article( $page->getTitle(), 0 );
407 $article->getContext()->getOutput()->setTitle( $page->getTitle() );
408
409 // use ArticleViewHeader hook to bypass the parser cache
410 $this->setTemporaryHook(
411 'ArticleViewHeader',
412 function ( Article $articlePage, &$outputDone, &$useParserCache ) use ( $article ) {
413 $useParserCache = false;
414 }
415 );
416
417 $this->setTemporaryHook(
418 'ArticleContentViewCustom',
419 function ( Content $content, Title $title, OutputPage $output ) use ( $page ) {
420 $this->assertSame( $page->getTitle(), $title, '$title' );
421 $this->assertSame( 'Test A', $content->getNativeData(), '$content' );
422
423 $output->addHTML( 'Hook Text' );
424 return false;
425 }
426 );
427
428 $this->hideDeprecated(
429 'ArticleContentViewCustom hook (used in hook-ArticleContentViewCustom-closure)'
430 );
431
432 $article->view();
433
434 $output = $article->getContext()->getOutput();
435 $this->assertNotContains( 'Test A', $this->getHtml( $output ) );
436 $this->assertContains( 'Hook Text', $this->getHtml( $output ) );
437 }
438
439 public function testArticleRevisionViewCustomHook() {
440 $page = $this->getPage( __METHOD__, [ 1 => 'Test A' ] );
441
442 $article = new Article( $page->getTitle(), 0 );
443 $article->getContext()->getOutput()->setTitle( $page->getTitle() );
444
445 // use ArticleViewHeader hook to bypass the parser cache
446 $this->setTemporaryHook(
447 'ArticleViewHeader',
448 function ( Article $articlePage, &$outputDone, &$useParserCache ) use ( $article ) {
449 $useParserCache = false;
450 }
451 );
452
453 $this->setTemporaryHook(
454 'ArticleRevisionViewCustom',
455 function ( RevisionRecord $rev, Title $title, $oldid, OutputPage $output ) use ( $page ) {
456 $content = $rev->getContent( 'main' );
457
458 $this->assertSame( $page->getTitle(), $title, '$title' );
459 $this->assertSame( 'Test A', $content->getNativeData(), '$content' );
460
461 $output->addHTML( 'Hook Text' );
462 return false;
463 }
464 );
465
466 $article->view();
467
468 $output = $article->getContext()->getOutput();
469 $this->assertNotContains( 'Test A', $this->getHtml( $output ) );
470 $this->assertContains( 'Hook Text', $this->getHtml( $output ) );
471 }
472
473 public function testArticleAfterFetchContentObjectHook() {
474 $page = $this->getPage( __METHOD__, [ 1 => 'Test A' ] );
475
476 $article = new Article( $page->getTitle(), 0 );
477 $article->getContext()->getOutput()->setTitle( $page->getTitle() );
478
479 // use ArticleViewHeader hook to bypass the parser cache
480 $this->setTemporaryHook(
481 'ArticleViewHeader',
482 function ( Article $articlePage, &$outputDone, &$useParserCache ) use ( $article ) {
483 $useParserCache = false;
484 }
485 );
486
487 $this->setTemporaryHook(
488 'ArticleAfterFetchContentObject',
489 function ( Article &$articlePage, Content &$content ) use ( $page, $article ) {
490 $this->assertSame( $article, $articlePage, '$articlePage' );
491 $this->assertSame( 'Test A', $content->getNativeData(), '$content' );
492
493 $content = new WikitextContent( 'Hook Text' );
494 }
495 );
496
497 $this->hideDeprecated(
498 'ArticleAfterFetchContentObject hook'
499 . ' (used in hook-ArticleAfterFetchContentObject-closure)'
500 );
501
502 $article->view();
503
504 $output = $article->getContext()->getOutput();
505 $this->assertNotContains( 'Test A', $this->getHtml( $output ) );
506 $this->assertContains( 'Hook Text', $this->getHtml( $output ) );
507 }
508
509 public function testShowMissingArticleHook() {
510 $page = $this->getPage( __METHOD__ );
511
512 $article = new Article( $page->getTitle() );
513 $article->getContext()->getOutput()->setTitle( $page->getTitle() );
514
515 $this->setTemporaryHook(
516 'ShowMissingArticle',
517 function ( Article $articlePage ) use ( $article ) {
518 $this->assertSame( $article, $articlePage, '$articlePage' );
519
520 $articlePage->getContext()->getOutput()->addHTML( 'Hook Text' );
521 }
522 );
523
524 $article->view();
525
526 $output = $article->getContext()->getOutput();
527 $this->assertContains( '(noarticletextanon)', $this->getHtml( $output ) );
528 $this->assertContains( 'Hook Text', $this->getHtml( $output ) );
529 }
530
531 }