MediaWiki  REL1_31
ApiEditPageTest.php
Go to the documentation of this file.
1 <?php
2 
15 
16  protected function setUp() {
18 
19  parent::setUp();
20 
21  $this->setMwGlobals( [
22  'wgExtraNamespaces' => $wgExtraNamespaces,
23  'wgNamespaceContentModels' => $wgNamespaceContentModels,
24  'wgContentHandlers' => $wgContentHandlers,
25  'wgContLang' => $wgContLang,
26  ] );
27 
28  $wgExtraNamespaces[12312] = 'Dummy';
29  $wgExtraNamespaces[12313] = 'Dummy_talk';
30  $wgExtraNamespaces[12314] = 'DummyNonText';
31  $wgExtraNamespaces[12315] = 'DummyNonText_talk';
32 
33  $wgNamespaceContentModels[12312] = "testing";
34  $wgNamespaceContentModels[12314] = "testing-nontext";
35 
36  $wgContentHandlers["testing"] = 'DummyContentHandlerForTesting';
37  $wgContentHandlers["testing-nontext"] = 'DummyNonTextContentHandler';
38  $wgContentHandlers["testing-serialize-error"] =
39  'DummySerializeErrorContentHandler';
40 
42  $wgContLang->resetNamespaces(); # reset namespace cache
43  }
44 
45  protected function tearDown() {
47 
49  $wgContLang->resetNamespaces(); # reset namespace cache
50 
51  parent::tearDown();
52  }
53 
54  public function testEdit() {
55  $name = 'Help:ApiEditPageTest_testEdit'; // assume Help namespace to default to wikitext
56 
57  // -- test new page --------------------------------------------
58  $apiResult = $this->doApiRequestWithToken( [
59  'action' => 'edit',
60  'title' => $name,
61  'text' => 'some text',
62  ] );
63  $apiResult = $apiResult[0];
64 
65  // Validate API result data
66  $this->assertArrayHasKey( 'edit', $apiResult );
67  $this->assertArrayHasKey( 'result', $apiResult['edit'] );
68  $this->assertSame( 'Success', $apiResult['edit']['result'] );
69 
70  $this->assertArrayHasKey( 'new', $apiResult['edit'] );
71  $this->assertArrayNotHasKey( 'nochange', $apiResult['edit'] );
72 
73  $this->assertArrayHasKey( 'pageid', $apiResult['edit'] );
74 
75  // -- test existing page, no change ----------------------------
76  $data = $this->doApiRequestWithToken( [
77  'action' => 'edit',
78  'title' => $name,
79  'text' => 'some text',
80  ] );
81 
82  $this->assertSame( 'Success', $data[0]['edit']['result'] );
83 
84  $this->assertArrayNotHasKey( 'new', $data[0]['edit'] );
85  $this->assertArrayHasKey( 'nochange', $data[0]['edit'] );
86 
87  // -- test existing page, with change --------------------------
88  $data = $this->doApiRequestWithToken( [
89  'action' => 'edit',
90  'title' => $name,
91  'text' => 'different text'
92  ] );
93 
94  $this->assertSame( 'Success', $data[0]['edit']['result'] );
95 
96  $this->assertArrayNotHasKey( 'new', $data[0]['edit'] );
97  $this->assertArrayNotHasKey( 'nochange', $data[0]['edit'] );
98 
99  $this->assertArrayHasKey( 'oldrevid', $data[0]['edit'] );
100  $this->assertArrayHasKey( 'newrevid', $data[0]['edit'] );
101  $this->assertNotEquals(
102  $data[0]['edit']['newrevid'],
103  $data[0]['edit']['oldrevid'],
104  "revision id should change after edit"
105  );
106  }
107 
111  public static function provideEditAppend() {
112  return [
113  [ # 0: append
114  'foo', 'append', 'bar', "foobar"
115  ],
116  [ # 1: prepend
117  'foo', 'prepend', 'bar', "barfoo"
118  ],
119  [ # 2: append to empty page
120  '', 'append', 'foo', "foo"
121  ],
122  [ # 3: prepend to empty page
123  '', 'prepend', 'foo', "foo"
124  ],
125  [ # 4: append to non-existing page
126  null, 'append', 'foo', "foo"
127  ],
128  [ # 5: prepend to non-existing page
129  null, 'prepend', 'foo', "foo"
130  ],
131  ];
132  }
133 
137  public function testEditAppend( $text, $op, $append, $expected ) {
138  static $count = 0;
139  $count++;
140 
141  // assume NS_HELP defaults to wikitext
142  $name = "Help:ApiEditPageTest_testEditAppend_$count";
143 
144  // -- create page (or not) -----------------------------------------
145  if ( $text !== null ) {
146  list( $re ) = $this->doApiRequestWithToken( [
147  'action' => 'edit',
148  'title' => $name,
149  'text' => $text, ] );
150 
151  $this->assertSame( 'Success', $re['edit']['result'] ); // sanity
152  }
153 
154  // -- try append/prepend --------------------------------------------
155  list( $re ) = $this->doApiRequestWithToken( [
156  'action' => 'edit',
157  'title' => $name,
158  $op . 'text' => $append, ] );
159 
160  $this->assertSame( 'Success', $re['edit']['result'] );
161 
162  // -- validate -----------------------------------------------------
163  $page = new WikiPage( Title::newFromText( $name ) );
164  $content = $page->getContent();
165  $this->assertNotNull( $content, 'Page should have been created' );
166 
167  $text = $content->getNativeData();
168 
169  $this->assertSame( $expected, $text );
170  }
171 
175  public function testEditSection() {
176  $name = 'Help:ApiEditPageTest_testEditSection';
178  $text = "==section 1==\ncontent 1\n==section 2==\ncontent2";
179  // Preload the page with some text
180  $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ), 'summary' );
181 
182  list( $re ) = $this->doApiRequestWithToken( [
183  'action' => 'edit',
184  'title' => $name,
185  'section' => '1',
186  'text' => "==section 1==\nnew content 1",
187  ] );
188  $this->assertSame( 'Success', $re['edit']['result'] );
190  ->getContent( Revision::RAW )
191  ->getNativeData();
192  $this->assertSame( "==section 1==\nnew content 1\n\n==section 2==\ncontent2", $newtext );
193 
194  // Test that we raise a 'nosuchsection' error
195  try {
196  $this->doApiRequestWithToken( [
197  'action' => 'edit',
198  'title' => $name,
199  'section' => '9999',
200  'text' => 'text',
201  ] );
202  $this->fail( "Should have raised an ApiUsageException" );
203  } catch ( ApiUsageException $e ) {
204  $this->assertTrue( self::apiExceptionHasCode( $e, 'nosuchsection' ) );
205  }
206  }
207 
214  public function testEditNewSection() {
215  $name = 'Help:ApiEditPageTest_testEditNewSection';
216 
217  // Test on a page that does not already exist
218  $this->assertFalse( Title::newFromText( $name )->exists() );
219  list( $re ) = $this->doApiRequestWithToken( [
220  'action' => 'edit',
221  'title' => $name,
222  'section' => 'new',
223  'text' => 'test',
224  'summary' => 'header',
225  ] );
226 
227  $this->assertSame( 'Success', $re['edit']['result'] );
228  // Check the page text is correct
230  ->getContent( Revision::RAW )
231  ->getNativeData();
232  $this->assertSame( "== header ==\n\ntest", $text );
233 
234  // Now on one that does
235  $this->assertTrue( Title::newFromText( $name )->exists() );
236  list( $re2 ) = $this->doApiRequestWithToken( [
237  'action' => 'edit',
238  'title' => $name,
239  'section' => 'new',
240  'text' => 'test',
241  'summary' => 'header',
242  ] );
243 
244  $this->assertSame( 'Success', $re2['edit']['result'] );
246  ->getContent( Revision::RAW )
247  ->getNativeData();
248  $this->assertSame( "== header ==\n\ntest\n\n== header ==\n\ntest", $text );
249  }
250 
254  public function testEdit_redirect() {
255  static $count = 0;
256  $count++;
257 
258  // assume NS_HELP defaults to wikitext
259  $name = "Help:ApiEditPageTest_testEdit_redirect_$count";
261  $page = WikiPage::factory( $title );
262 
263  $rname = "Help:ApiEditPageTest_testEdit_redirect_r$count";
264  $rtitle = Title::newFromText( $rname );
265  $rpage = WikiPage::factory( $rtitle );
266 
267  // base edit for content
268  $page->doEditContent( new WikitextContent( "Foo" ),
269  "testing 1", EDIT_NEW, false, self::$users['sysop']->getUser() );
270  $this->forceRevisionDate( $page, '20120101000000' );
271  $baseTime = $page->getRevision()->getTimestamp();
272 
273  // base edit for redirect
274  $rpage->doEditContent( new WikitextContent( "#REDIRECT [[$name]]" ),
275  "testing 1", EDIT_NEW, false, self::$users['sysop']->getUser() );
276  $this->forceRevisionDate( $rpage, '20120101000000' );
277 
278  // conflicting edit to redirect
279  $rpage->doEditContent( new WikitextContent( "#REDIRECT [[$name]]\n\n[[Category:Test]]" ),
280  "testing 2", EDIT_UPDATE, $page->getLatest(), self::$users['uploader']->getUser() );
281  $this->forceRevisionDate( $rpage, '20120101020202' );
282 
283  // try to save edit, following the redirect
284  list( $re, , ) = $this->doApiRequestWithToken( [
285  'action' => 'edit',
286  'title' => $rname,
287  'text' => 'nix bar!',
288  'basetimestamp' => $baseTime,
289  'section' => 'new',
290  'redirect' => true,
291  ] );
292 
293  $this->assertSame( 'Success', $re['edit']['result'],
294  "no problems expected when following redirect" );
295  }
296 
300  public function testEdit_redirectText() {
301  static $count = 0;
302  $count++;
303 
304  // assume NS_HELP defaults to wikitext
305  $name = "Help:ApiEditPageTest_testEdit_redirectText_$count";
307  $page = WikiPage::factory( $title );
308 
309  $rname = "Help:ApiEditPageTest_testEdit_redirectText_r$count";
310  $rtitle = Title::newFromText( $rname );
311  $rpage = WikiPage::factory( $rtitle );
312 
313  // base edit for content
314  $page->doEditContent( new WikitextContent( "Foo" ),
315  "testing 1", EDIT_NEW, false, self::$users['sysop']->getUser() );
316  $this->forceRevisionDate( $page, '20120101000000' );
317  $baseTime = $page->getRevision()->getTimestamp();
318 
319  // base edit for redirect
320  $rpage->doEditContent( new WikitextContent( "#REDIRECT [[$name]]" ),
321  "testing 1", EDIT_NEW, false, self::$users['sysop']->getUser() );
322  $this->forceRevisionDate( $rpage, '20120101000000' );
323 
324  // conflicting edit to redirect
325  $rpage->doEditContent( new WikitextContent( "#REDIRECT [[$name]]\n\n[[Category:Test]]" ),
326  "testing 2", EDIT_UPDATE, $page->getLatest(), self::$users['uploader']->getUser() );
327  $this->forceRevisionDate( $rpage, '20120101020202' );
328 
329  // try to save edit, following the redirect but without creating a section
330  try {
331  $this->doApiRequestWithToken( [
332  'action' => 'edit',
333  'title' => $rname,
334  'text' => 'nix bar!',
335  'basetimestamp' => $baseTime,
336  'redirect' => true,
337  ] );
338 
339  $this->fail( 'redirect-appendonly error expected' );
340  } catch ( ApiUsageException $ex ) {
341  $this->assertTrue( self::apiExceptionHasCode( $ex, 'redirect-appendonly' ) );
342  }
343  }
344 
345  public function testEditConflict() {
346  static $count = 0;
347  $count++;
348 
349  // assume NS_HELP defaults to wikitext
350  $name = "Help:ApiEditPageTest_testEditConflict_$count";
352 
353  $page = WikiPage::factory( $title );
354 
355  // base edit
356  $page->doEditContent( new WikitextContent( "Foo" ),
357  "testing 1", EDIT_NEW, false, self::$users['sysop']->getUser() );
358  $this->forceRevisionDate( $page, '20120101000000' );
359  $baseTime = $page->getRevision()->getTimestamp();
360 
361  // conflicting edit
362  $page->doEditContent( new WikitextContent( "Foo bar" ),
363  "testing 2", EDIT_UPDATE, $page->getLatest(), self::$users['uploader']->getUser() );
364  $this->forceRevisionDate( $page, '20120101020202' );
365 
366  // try to save edit, expect conflict
367  try {
368  $this->doApiRequestWithToken( [
369  'action' => 'edit',
370  'title' => $name,
371  'text' => 'nix bar!',
372  'basetimestamp' => $baseTime,
373  ] );
374 
375  $this->fail( 'edit conflict expected' );
376  } catch ( ApiUsageException $ex ) {
377  $this->assertTrue( self::apiExceptionHasCode( $ex, 'editconflict' ) );
378  }
379  }
380 
384  public function testEditConflict_newSection() {
385  static $count = 0;
386  $count++;
387 
388  // assume NS_HELP defaults to wikitext
389  $name = "Help:ApiEditPageTest_testEditConflict_newSection_$count";
391 
392  $page = WikiPage::factory( $title );
393 
394  // base edit
395  $page->doEditContent( new WikitextContent( "Foo" ),
396  "testing 1", EDIT_NEW, false, self::$users['sysop']->getUser() );
397  $this->forceRevisionDate( $page, '20120101000000' );
398  $baseTime = $page->getRevision()->getTimestamp();
399 
400  // conflicting edit
401  $page->doEditContent( new WikitextContent( "Foo bar" ),
402  "testing 2", EDIT_UPDATE, $page->getLatest(), self::$users['uploader']->getUser() );
403  $this->forceRevisionDate( $page, '20120101020202' );
404 
405  // try to save edit, expect no conflict
406  list( $re, , ) = $this->doApiRequestWithToken( [
407  'action' => 'edit',
408  'title' => $name,
409  'text' => 'nix bar!',
410  'basetimestamp' => $baseTime,
411  'section' => 'new',
412  ] );
413 
414  $this->assertSame( 'Success', $re['edit']['result'],
415  "no edit conflict expected here" );
416  }
417 
418  public function testEditConflict_bug41990() {
419  static $count = 0;
420  $count++;
421 
422  /*
423  * T43990: if the target page has a newer revision than the redirect, then editing the
424  * redirect while specifying 'redirect' and *not* specifying 'basetimestamp' erroneously
425  * caused an edit conflict to be detected.
426  */
427 
428  // assume NS_HELP defaults to wikitext
429  $name = "Help:ApiEditPageTest_testEditConflict_redirect_bug41990_$count";
431  $page = WikiPage::factory( $title );
432 
433  $rname = "Help:ApiEditPageTest_testEditConflict_redirect_bug41990_r$count";
434  $rtitle = Title::newFromText( $rname );
435  $rpage = WikiPage::factory( $rtitle );
436 
437  // base edit for content
438  $page->doEditContent( new WikitextContent( "Foo" ),
439  "testing 1", EDIT_NEW, false, self::$users['sysop']->getUser() );
440  $this->forceRevisionDate( $page, '20120101000000' );
441 
442  // base edit for redirect
443  $rpage->doEditContent( new WikitextContent( "#REDIRECT [[$name]]" ),
444  "testing 1", EDIT_NEW, false, self::$users['sysop']->getUser() );
445  $this->forceRevisionDate( $rpage, '20120101000000' );
446 
447  // new edit to content
448  $page->doEditContent( new WikitextContent( "Foo bar" ),
449  "testing 2", EDIT_UPDATE, $page->getLatest(), self::$users['uploader']->getUser() );
450  $this->forceRevisionDate( $rpage, '20120101020202' );
451 
452  // try to save edit; should work, following the redirect.
453  list( $re, , ) = $this->doApiRequestWithToken( [
454  'action' => 'edit',
455  'title' => $rname,
456  'text' => 'nix bar!',
457  'section' => 'new',
458  'redirect' => true,
459  ] );
460 
461  $this->assertSame( 'Success', $re['edit']['result'],
462  "no edit conflict expected here" );
463  }
464 
469  protected function forceRevisionDate( WikiPage $page, $timestamp ) {
470  $dbw = wfGetDB( DB_MASTER );
471 
472  $dbw->update( 'revision',
473  [ 'rev_timestamp' => $dbw->timestamp( $timestamp ) ],
474  [ 'rev_id' => $page->getLatest() ] );
475 
476  $page->clear();
477  }
478 
480  $this->setExpectedException(
482  'Direct editing via API is not supported for content model ' .
483  'testing used by Dummy:ApiEditPageTest_nonTextPageEdit'
484  );
485 
486  $this->doApiRequestWithToken( [
487  'action' => 'edit',
488  'title' => 'Dummy:ApiEditPageTest_nonTextPageEdit',
489  'text' => '{"animals":["kittens!"]}'
490  ] );
491  }
492 
494  $name = 'DummyNonText:ApiEditPageTest_testNonTextEdit';
495  $data = serialize( 'some bla bla text' );
496 
497  $result = $this->doApiRequestWithToken( [
498  'action' => 'edit',
499  'title' => $name,
500  'text' => $data,
501  ] );
502 
503  $apiResult = $result[0];
504 
505  // Validate API result data
506  $this->assertArrayHasKey( 'edit', $apiResult );
507  $this->assertArrayHasKey( 'result', $apiResult['edit'] );
508  $this->assertSame( 'Success', $apiResult['edit']['result'] );
509 
510  $this->assertArrayHasKey( 'new', $apiResult['edit'] );
511  $this->assertArrayNotHasKey( 'nochange', $apiResult['edit'] );
512 
513  $this->assertArrayHasKey( 'pageid', $apiResult['edit'] );
514 
515  // validate resulting revision
517  $this->assertSame( "testing-nontext", $page->getContentModel() );
518  $this->assertSame( $data, $page->getContent()->serialize() );
519  }
520 
527  $name = 'Help:' . __FUNCTION__;
528  $uploader = self::$users['uploader']->getUser();
529  $sysop = self::$users['sysop']->getUser();
530 
531  $apiResult = $this->doApiRequestWithToken( [
532  'action' => 'edit',
533  'title' => $name,
534  'text' => 'some text',
535  ], null, $sysop )[0];
536 
537  // Check success
538  $this->assertArrayHasKey( 'edit', $apiResult );
539  $this->assertArrayHasKey( 'result', $apiResult['edit'] );
540  $this->assertSame( 'Success', $apiResult['edit']['result'] );
541  $this->assertArrayHasKey( 'contentmodel', $apiResult['edit'] );
542  // Content model is wikitext
543  $this->assertSame( 'wikitext', $apiResult['edit']['contentmodel'] );
544 
545  // Convert the page to JSON
546  $apiResult = $this->doApiRequestWithToken( [
547  'action' => 'edit',
548  'title' => $name,
549  'text' => '{}',
550  'contentmodel' => 'json',
551  ], null, $uploader )[0];
552 
553  // Check success
554  $this->assertArrayHasKey( 'edit', $apiResult );
555  $this->assertArrayHasKey( 'result', $apiResult['edit'] );
556  $this->assertSame( 'Success', $apiResult['edit']['result'] );
557  $this->assertArrayHasKey( 'contentmodel', $apiResult['edit'] );
558  $this->assertSame( 'json', $apiResult['edit']['contentmodel'] );
559 
560  $apiResult = $this->doApiRequestWithToken( [
561  'action' => 'edit',
562  'title' => $name,
563  'undo' => $apiResult['edit']['newrevid']
564  ], null, $sysop )[0];
565 
566  // Check success
567  $this->assertArrayHasKey( 'edit', $apiResult );
568  $this->assertArrayHasKey( 'result', $apiResult['edit'] );
569  $this->assertSame( 'Success', $apiResult['edit']['result'] );
570  $this->assertArrayHasKey( 'contentmodel', $apiResult['edit'] );
571  // Check that the contentmodel is back to wikitext now.
572  $this->assertSame( 'wikitext', $apiResult['edit']['contentmodel'] );
573  }
574 
575  // The tests below are mostly not commented because they do exactly what
576  // you'd expect from the name.
577 
578  public function testCorrectContentFormat() {
579  $name = 'Help:' . ucfirst( __FUNCTION__ );
580 
581  $this->doApiRequestWithToken( [
582  'action' => 'edit',
583  'title' => $name,
584  'text' => 'some text',
585  'contentmodel' => 'wikitext',
586  'contentformat' => 'text/x-wiki',
587  ] );
588 
589  $this->assertTrue( Title::newFromText( $name )->exists() );
590  }
591 
592  public function testUnsupportedContentFormat() {
593  $name = 'Help:' . ucfirst( __FUNCTION__ );
594 
595  $this->setExpectedException( ApiUsageException::class,
596  'Unrecognized value for parameter "contentformat": nonexistent format.' );
597 
598  try {
599  $this->doApiRequestWithToken( [
600  'action' => 'edit',
601  'title' => $name,
602  'text' => 'some text',
603  'contentformat' => 'nonexistent format',
604  ] );
605  } finally {
606  $this->assertFalse( Title::newFromText( $name )->exists() );
607  }
608  }
609 
610  public function testMismatchedContentFormat() {
611  $name = 'Help:' . ucfirst( __FUNCTION__ );
612 
613  $this->setExpectedException( ApiUsageException::class,
614  'The requested format text/plain is not supported for content ' .
615  "model wikitext used by $name." );
616 
617  try {
618  $this->doApiRequestWithToken( [
619  'action' => 'edit',
620  'title' => $name,
621  'text' => 'some text',
622  'contentmodel' => 'wikitext',
623  'contentformat' => 'text/plain',
624  ] );
625  } finally {
626  $this->assertFalse( Title::newFromText( $name )->exists() );
627  }
628  }
629 
630  public function testUndoToInvalidRev() {
631  $name = 'Help:' . ucfirst( __FUNCTION__ );
632 
633  $revId = $this->editPage( $name, 'Some text' )->value['revision']
634  ->getId();
635  $revId++;
636 
637  $this->setExpectedException( ApiUsageException::class,
638  "There is no revision with ID $revId." );
639 
640  $this->doApiRequestWithToken( [
641  'action' => 'edit',
642  'title' => $name,
643  'undo' => $revId,
644  ] );
645  }
646 
652  public function testUndoAfterToInvalidRev() {
653  // We can't just pick a large number for undoafter (as in
654  // testUndoToInvalidRev above), because then MediaWiki will helpfully
655  // assume we switched around undo and undoafter and we'll test the code
656  // path for undo being invalid, not undoafter. So instead we delete
657  // the revision from the database. In real life this case could come
658  // up if a revision number was skipped, e.g., if two transactions try
659  // to insert new revision rows at once and the first one to succeed
660  // gets rolled back.
661  $name = 'Help:' . ucfirst( __FUNCTION__ );
662  $titleObj = Title::newFromText( $name );
663 
664  $revId1 = $this->editPage( $name, '1' )->value['revision']->getId();
665  $revId2 = $this->editPage( $name, '2' )->value['revision']->getId();
666  $revId3 = $this->editPage( $name, '3' )->value['revision']->getId();
667 
668  // Make the middle revision disappear
669  $dbw = wfGetDB( DB_MASTER );
670  $dbw->delete( 'revision', [ 'rev_id' => $revId2 ], __METHOD__ );
671  $dbw->update( 'revision', [ 'rev_parent_id' => $revId1 ],
672  [ 'rev_id' => $revId3 ], __METHOD__ );
673 
674  $this->setExpectedException( ApiUsageException::class,
675  "There is no revision with ID $revId2." );
676 
677  $this->doApiRequestWithToken( [
678  'action' => 'edit',
679  'title' => $name,
680  'undo' => $revId3,
681  'undoafter' => $revId2,
682  ] );
683  }
684 
689  public function testUndoAfterToHiddenRev() {
690  $name = 'Help:' . ucfirst( __FUNCTION__ );
691  $titleObj = Title::newFromText( $name );
692 
693  $this->editPage( $name, '0' );
694 
695  $revId1 = $this->editPage( $name, '1' )->value['revision']->getId();
696 
697  $revId2 = $this->editPage( $name, '2' )->value['revision']->getId();
698 
699  // Hide the middle revision
700  $list = RevisionDeleter::createList( 'revision',
701  RequestContext::getMain(), $titleObj, [ $revId1 ] );
702  $list->setVisibility( [
703  'value' => [ Revision::DELETED_TEXT => 1 ],
704  'comment' => 'Bye-bye',
705  ] );
706 
707  $this->setExpectedException( ApiUsageException::class,
708  "There is no revision with ID $revId1." );
709 
710  $this->doApiRequestWithToken( [
711  'action' => 'edit',
712  'title' => $name,
713  'undo' => $revId2,
714  'undoafter' => $revId1,
715  ] );
716  }
717 
722  public function testUndoWithSwappedRevisions() {
723  $name = 'Help:' . ucfirst( __FUNCTION__ );
724  $titleObj = Title::newFromText( $name );
725 
726  $this->editPage( $name, '0' );
727 
728  $revId2 = $this->editPage( $name, '2' )->value['revision']->getId();
729 
730  $revId1 = $this->editPage( $name, '1' )->value['revision']->getId();
731 
732  // Now monkey with the timestamp
733  $dbw = wfGetDB( DB_MASTER );
734  $dbw->update(
735  'revision',
736  [ 'rev_timestamp' => wfTimestamp( TS_MW, time() - 86400 ) ],
737  [ 'rev_id' => $revId1 ],
738  __METHOD__
739  );
740 
741  $this->doApiRequestWithToken( [
742  'action' => 'edit',
743  'title' => $name,
744  'undo' => $revId2,
745  'undoafter' => $revId1,
746  ] );
747 
748  $text = ( new WikiPage( $titleObj ) )->getContent()->getNativeData();
749 
750  // This is wrong! It should be 1. But let's test for our incorrect
751  // behavior for now, so if someone fixes it they'll fix the test as
752  // well to expect 1. If we disabled the test, it might stay disabled
753  // even once the bug is fixed, which would be a shame.
754  $this->assertSame( '2', $text );
755  }
756 
757  public function testUndoWithConflicts() {
758  $name = 'Help:' . ucfirst( __FUNCTION__ );
759 
760  $this->setExpectedException( ApiUsageException::class,
761  'The edit could not be undone due to conflicting intermediate edits.' );
762 
763  $this->editPage( $name, '1' );
764 
765  $revId = $this->editPage( $name, '2' )->value['revision']->getId();
766 
767  $this->editPage( $name, '3' );
768 
769  $this->doApiRequestWithToken( [
770  'action' => 'edit',
771  'title' => $name,
772  'undo' => $revId,
773  ] );
774 
775  $text = ( new WikiPage( Title::newFromText( $name ) ) )->getContent()
776  ->getNativeData();
777  $this->assertSame( '3', $text );
778  }
779 
784  public function testReversedUndoAfter() {
785  $name = 'Help:' . ucfirst( __FUNCTION__ );
786 
787  $this->editPage( $name, '0' );
788  $revId1 = $this->editPage( $name, '1' )->value['revision']->getId();
789  $revId2 = $this->editPage( $name, '2' )->value['revision']->getId();
790 
791  $this->doApiRequestWithToken( [
792  'action' => 'edit',
793  'title' => $name,
794  'undo' => $revId1,
795  'undoafter' => $revId2,
796  ] );
797 
798  $text = ( new WikiPage( Title::newFromText( $name ) ) )->getContent()
799  ->getNativeData();
800  $this->assertSame( '1', $text );
801  }
802 
803  public function testUndoToRevFromDifferentPage() {
804  $name = 'Help:' . ucfirst( __FUNCTION__ );
805 
806  $this->editPage( "$name-1", 'Some text' );
807  $revId = $this->editPage( "$name-1", 'Some more text' )
808  ->value['revision']->getId();
809 
810  $this->editPage( "$name-2", 'Some text' );
811 
812  $this->setExpectedException( ApiUsageException::class,
813  "r$revId is not a revision of $name-2." );
814 
815  $this->doApiRequestWithToken( [
816  'action' => 'edit',
817  'title' => "$name-2",
818  'undo' => $revId,
819  ] );
820  }
821 
823  $name = 'Help:' . ucfirst( __FUNCTION__ );
824 
825  $revId1 = $this->editPage( "$name-1", 'Some text' )
826  ->value['revision']->getId();
827 
828  $revId2 = $this->editPage( "$name-2", 'Some text' )
829  ->value['revision']->getId();
830 
831  $this->setExpectedException( ApiUsageException::class,
832  "r$revId1 is not a revision of $name-2." );
833 
834  $this->doApiRequestWithToken( [
835  'action' => 'edit',
836  'title' => "$name-2",
837  'undo' => $revId2,
838  'undoafter' => $revId1,
839  ] );
840  }
841 
842  public function testMd5Text() {
843  $name = 'Help:' . ucfirst( __FUNCTION__ );
844 
845  $this->assertFalse( Title::newFromText( $name )->exists() );
846 
847  $this->doApiRequestWithToken( [
848  'action' => 'edit',
849  'title' => $name,
850  'text' => 'Some text',
851  'md5' => md5( 'Some text' ),
852  ] );
853 
854  $this->assertTrue( Title::newFromText( $name )->exists() );
855  }
856 
857  public function testMd5PrependText() {
858  $name = 'Help:' . ucfirst( __FUNCTION__ );
859 
860  $this->editPage( $name, 'Some text' );
861 
862  $this->doApiRequestWithToken( [
863  'action' => 'edit',
864  'title' => $name,
865  'prependtext' => 'Alert: ',
866  'md5' => md5( 'Alert: ' ),
867  ] );
868 
869  $text = ( new WikiPage( Title::newFromText( $name ) ) )
870  ->getContent()->getNativeData();
871  $this->assertSame( 'Alert: Some text', $text );
872  }
873 
874  public function testMd5AppendText() {
875  $name = 'Help:' . ucfirst( __FUNCTION__ );
876 
877  $this->editPage( $name, 'Some text' );
878 
879  $this->doApiRequestWithToken( [
880  'action' => 'edit',
881  'title' => $name,
882  'appendtext' => ' is nice',
883  'md5' => md5( ' is nice' ),
884  ] );
885 
886  $text = ( new WikiPage( Title::newFromText( $name ) ) )
887  ->getContent()->getNativeData();
888  $this->assertSame( 'Some text is nice', $text );
889  }
890 
891  public function testMd5PrependAndAppendText() {
892  $name = 'Help:' . ucfirst( __FUNCTION__ );
893 
894  $this->editPage( $name, 'Some text' );
895 
896  $this->doApiRequestWithToken( [
897  'action' => 'edit',
898  'title' => $name,
899  'prependtext' => 'Alert: ',
900  'appendtext' => ' is nice',
901  'md5' => md5( 'Alert: is nice' ),
902  ] );
903 
904  $text = ( new WikiPage( Title::newFromText( $name ) ) )
905  ->getContent()->getNativeData();
906  $this->assertSame( 'Alert: Some text is nice', $text );
907  }
908 
909  public function testIncorrectMd5Text() {
910  $name = 'Help:' . ucfirst( __FUNCTION__ );
911 
912  $this->setExpectedException( ApiUsageException::class,
913  'The supplied MD5 hash was incorrect.' );
914 
915  $this->doApiRequestWithToken( [
916  'action' => 'edit',
917  'title' => $name,
918  'text' => 'Some text',
919  'md5' => md5( '' ),
920  ] );
921  }
922 
923  public function testIncorrectMd5PrependText() {
924  $name = 'Help:' . ucfirst( __FUNCTION__ );
925 
926  $this->setExpectedException( ApiUsageException::class,
927  'The supplied MD5 hash was incorrect.' );
928 
929  $this->doApiRequestWithToken( [
930  'action' => 'edit',
931  'title' => $name,
932  'prependtext' => 'Some ',
933  'appendtext' => 'text',
934  'md5' => md5( 'Some ' ),
935  ] );
936  }
937 
938  public function testIncorrectMd5AppendText() {
939  $name = 'Help:' . ucfirst( __FUNCTION__ );
940 
941  $this->setExpectedException( ApiUsageException::class,
942  'The supplied MD5 hash was incorrect.' );
943 
944  $this->doApiRequestWithToken( [
945  'action' => 'edit',
946  'title' => $name,
947  'prependtext' => 'Some ',
948  'appendtext' => 'text',
949  'md5' => md5( 'text' ),
950  ] );
951  }
952 
953  public function testCreateOnly() {
954  $name = 'Help:' . ucfirst( __FUNCTION__ );
955 
956  $this->setExpectedException( ApiUsageException::class,
957  'The article you tried to create has been created already.' );
958 
959  $this->editPage( $name, 'Some text' );
960  $this->assertTrue( Title::newFromText( $name )->exists() );
961 
962  try {
963  $this->doApiRequestWithToken( [
964  'action' => 'edit',
965  'title' => $name,
966  'text' => 'Some more text',
967  'createonly' => '',
968  ] );
969  } finally {
970  // Validate that content was not changed
971  $text = ( new WikiPage( Title::newFromText( $name ) ) )
972  ->getContent()->getNativeData();
973 
974  $this->assertSame( 'Some text', $text );
975  }
976  }
977 
978  public function testNoCreate() {
979  $name = 'Help:' . ucfirst( __FUNCTION__ );
980 
981  $this->setExpectedException( ApiUsageException::class,
982  "The page you specified doesn't exist." );
983 
984  $this->assertFalse( Title::newFromText( $name )->exists() );
985 
986  try {
987  $this->doApiRequestWithToken( [
988  'action' => 'edit',
989  'title' => $name,
990  'text' => 'Some text',
991  'nocreate' => '',
992  ] );
993  } finally {
994  $this->assertFalse( Title::newFromText( $name )->exists() );
995  }
996  }
997 
1004  $name = 'MediaWiki:' . ucfirst( __FUNCTION__ );
1005 
1006  $this->setExpectedException( ApiUsageException::class,
1007  "Can't append to pages using content model testing-nontext." );
1008 
1009  $this->setTemporaryHook( 'ContentHandlerDefaultModelFor',
1010  function ( Title $title, &$model ) use ( $name ) {
1011  if ( $title->getPrefixedText() === $name ) {
1012  $model = 'testing-nontext';
1013  }
1014  return true;
1015  }
1016  );
1017 
1018  $this->doApiRequestWithToken( [
1019  'action' => 'edit',
1020  'title' => $name,
1021  'appendtext' => 'Some text',
1022  ] );
1023  }
1024 
1026  $name = 'MediaWiki:' . ucfirst( __FUNCTION__ );
1027 
1028  $this->assertFalse( Title::newFromText( $name )->exists() );
1029 
1030  $this->doApiRequestWithToken( [
1031  'action' => 'edit',
1032  'title' => $name,
1033  'appendtext' => 'Some text',
1034  ] );
1035 
1036  $this->assertTrue( Title::newFromText( $name )->exists() );
1037  }
1038 
1040  $name = 'MediaWiki:' . ucfirst( __FUNCTION__ );
1041 
1042  $this->setExpectedException( ApiUsageException::class,
1043  'Content serialization failed: Could not unserialize content' );
1044 
1045  $this->setTemporaryHook( 'ContentHandlerDefaultModelFor',
1046  function ( Title $title, &$model ) use ( $name ) {
1047  if ( $title->getPrefixedText() === $name ) {
1048  $model = 'testing-serialize-error';
1049  }
1050  return true;
1051  }
1052  );
1053 
1054  $this->doApiRequestWithToken( [
1055  'action' => 'edit',
1056  'title' => $name,
1057  'appendtext' => 'Some text',
1058  ] );
1059  }
1060 
1061  public function testAppendNewSection() {
1062  $name = 'Help:' . ucfirst( __FUNCTION__ );
1063 
1064  $this->editPage( $name, 'Initial content' );
1065 
1066  $this->doApiRequestWithToken( [
1067  'action' => 'edit',
1068  'title' => $name,
1069  'appendtext' => '== New section ==',
1070  'section' => 'new',
1071  ] );
1072 
1073  $text = ( new WikiPage( Title::newFromText( $name ) ) )
1074  ->getContent()->getNativeData();
1075 
1076  $this->assertSame( "Initial content\n\n== New section ==", $text );
1077  }
1078 
1080  $name = 'Help:' . ucfirst( __FUNCTION__ );
1081 
1082  $this->setExpectedException( ApiUsageException::class,
1083  'Sections are not supported for content model text.' );
1084 
1085  $this->editPage( $name, 'Initial content' );
1086 
1087  $this->doApiRequestWithToken( [
1088  'action' => 'edit',
1089  'title' => $name,
1090  'appendtext' => '== New section ==',
1091  'section' => 'new',
1092  'contentmodel' => 'text',
1093  ] );
1094  }
1095 
1096  public function testAppendNewSectionWithTitle() {
1097  $name = 'Help:' . ucfirst( __FUNCTION__ );
1098 
1099  $this->editPage( $name, 'Initial content' );
1100 
1101  $this->doApiRequestWithToken( [
1102  'action' => 'edit',
1103  'title' => $name,
1104  'sectiontitle' => 'My section',
1105  'appendtext' => 'More content',
1106  'section' => 'new',
1107  ] );
1108 
1109  $page = new WikiPage( Title::newFromText( $name ) );
1110 
1111  $this->assertSame( "Initial content\n\n== My section ==\n\nMore content",
1112  $page->getContent()->getNativeData() );
1113  $this->assertSame( '/* My section */ new section',
1114  $page->getRevision()->getComment() );
1115  }
1116 
1118  $name = 'Help:' . ucfirst( __FUNCTION__ );
1119 
1120  $this->editPage( $name, 'Initial content' );
1121 
1122  $this->doApiRequestWithToken( [
1123  'action' => 'edit',
1124  'title' => $name,
1125  'appendtext' => 'More content',
1126  'section' => 'new',
1127  'summary' => 'Add new section',
1128  ] );
1129 
1130  $page = new WikiPage( Title::newFromText( $name ) );
1131 
1132  $this->assertSame( "Initial content\n\n== Add new section ==\n\nMore content",
1133  $page->getContent()->getNativeData() );
1134  // EditPage actually assumes the summary is the section name here
1135  $this->assertSame( '/* Add new section */ new section',
1136  $page->getRevision()->getComment() );
1137  }
1138 
1140  $name = 'Help:' . ucfirst( __FUNCTION__ );
1141 
1142  $this->editPage( $name, 'Initial content' );
1143 
1144  $this->doApiRequestWithToken( [
1145  'action' => 'edit',
1146  'title' => $name,
1147  'sectiontitle' => 'My section',
1148  'appendtext' => 'More content',
1149  'section' => 'new',
1150  'summary' => 'Add new section',
1151  ] );
1152 
1153  $page = new WikiPage( Title::newFromText( $name ) );
1154 
1155  $this->assertSame( "Initial content\n\n== My section ==\n\nMore content",
1156  $page->getContent()->getNativeData() );
1157  $this->assertSame( 'Add new section',
1158  $page->getRevision()->getComment() );
1159  }
1160 
1161  public function testAppendToSection() {
1162  $name = 'Help:' . ucfirst( __FUNCTION__ );
1163 
1164  $this->editPage( $name, "== Section 1 ==\n\nContent\n\n" .
1165  "== Section 2 ==\n\nFascinating!" );
1166 
1167  $this->doApiRequestWithToken( [
1168  'action' => 'edit',
1169  'title' => $name,
1170  'appendtext' => ' and more content',
1171  'section' => '1',
1172  ] );
1173 
1174  $text = ( new WikiPage( Title::newFromText( $name ) ) )
1175  ->getContent()->getNativeData();
1176 
1177  $this->assertSame( "== Section 1 ==\n\nContent and more content\n\n" .
1178  "== Section 2 ==\n\nFascinating!", $text );
1179  }
1180 
1181  public function testAppendToFirstSection() {
1182  $name = 'Help:' . ucfirst( __FUNCTION__ );
1183 
1184  $this->editPage( $name, "Content\n\n== Section 1 ==\n\nFascinating!" );
1185 
1186  $this->doApiRequestWithToken( [
1187  'action' => 'edit',
1188  'title' => $name,
1189  'appendtext' => ' and more content',
1190  'section' => '0',
1191  ] );
1192 
1193  $text = ( new WikiPage( Title::newFromText( $name ) ) )
1194  ->getContent()->getNativeData();
1195 
1196  $this->assertSame( "Content and more content\n\n== Section 1 ==\n\n" .
1197  "Fascinating!", $text );
1198  }
1199 
1201  $name = 'Help:' . ucfirst( __FUNCTION__ );
1202 
1203  $this->setExpectedException( ApiUsageException::class, 'There is no section 1.' );
1204 
1205  $this->editPage( $name, 'Content' );
1206 
1207  try {
1208  $this->doApiRequestWithToken( [
1209  'action' => 'edit',
1210  'title' => $name,
1211  'appendtext' => ' and more content',
1212  'section' => '1',
1213  ] );
1214  } finally {
1215  $text = ( new WikiPage( Title::newFromText( $name ) ) )
1216  ->getContent()->getNativeData();
1217 
1218  $this->assertSame( 'Content', $text );
1219  }
1220  }
1221 
1222  public function testEditMalformedSection() {
1223  $name = 'Help:' . ucfirst( __FUNCTION__ );
1224 
1225  $this->setExpectedException( ApiUsageException::class,
1226  'The "section" parameter must be a valid section ID or "new".' );
1227  $this->editPage( $name, 'Content' );
1228 
1229  try {
1230  $this->doApiRequestWithToken( [
1231  'action' => 'edit',
1232  'title' => $name,
1233  'text' => 'Different content',
1234  'section' => 'It is unlikely that this is valid',
1235  ] );
1236  } finally {
1237  $text = ( new WikiPage( Title::newFromText( $name ) ) )
1238  ->getContent()->getNativeData();
1239 
1240  $this->assertSame( 'Content', $text );
1241  }
1242  }
1243 
1244  public function testEditWithStartTimestamp() {
1245  $name = 'Help:' . ucfirst( __FUNCTION__ );
1246  $this->setExpectedException( ApiUsageException::class,
1247  'The page has been deleted since you fetched its timestamp.' );
1248 
1249  $startTime = MWTimestamp::convert( TS_MW, time() - 1 );
1250 
1251  $this->editPage( $name, 'Some text' );
1252 
1253  $pageObj = new WikiPage( Title::newFromText( $name ) );
1254  $pageObj->doDeleteArticle( 'Bye-bye' );
1255 
1256  $this->assertFalse( $pageObj->exists() );
1257 
1258  try {
1259  $this->doApiRequestWithToken( [
1260  'action' => 'edit',
1261  'title' => $name,
1262  'text' => 'Different text',
1263  'starttimestamp' => $startTime,
1264  ] );
1265  } finally {
1266  $this->assertFalse( $pageObj->exists() );
1267  }
1268  }
1269 
1270  public function testEditMinor() {
1271  $name = 'Help:' . ucfirst( __FUNCTION__ );
1272 
1273  $this->editPage( $name, 'Some text' );
1274 
1275  $this->doApiRequestWithToken( [
1276  'action' => 'edit',
1277  'title' => $name,
1278  'text' => 'Different text',
1279  'minor' => '',
1280  ] );
1281 
1282  $revisionStore = \MediaWiki\MediaWikiServices::getInstance()->getRevisionStore();
1283  $revision = $revisionStore->getRevisionByTitle( Title::newFromText( $name ) );
1284  $this->assertTrue( $revision->isMinor() );
1285  }
1286 
1287  public function testEditRecreate() {
1288  $name = 'Help:' . ucfirst( __FUNCTION__ );
1289 
1290  $startTime = MWTimestamp::convert( TS_MW, time() - 1 );
1291 
1292  $this->editPage( $name, 'Some text' );
1293 
1294  $pageObj = new WikiPage( Title::newFromText( $name ) );
1295  $pageObj->doDeleteArticle( 'Bye-bye' );
1296 
1297  $this->assertFalse( $pageObj->exists() );
1298 
1299  $this->doApiRequestWithToken( [
1300  'action' => 'edit',
1301  'title' => $name,
1302  'text' => 'Different text',
1303  'starttimestamp' => $startTime,
1304  'recreate' => '',
1305  ] );
1306 
1307  $this->assertTrue( Title::newFromText( $name )->exists() );
1308  }
1309 
1310  public function testEditWatch() {
1311  $name = 'Help:' . ucfirst( __FUNCTION__ );
1312  $user = self::$users['sysop']->getUser();
1313 
1314  $this->doApiRequestWithToken( [
1315  'action' => 'edit',
1316  'title' => $name,
1317  'text' => 'Some text',
1318  'watch' => '',
1319  ] );
1320 
1321  $this->assertTrue( Title::newFromText( $name )->exists() );
1322  $this->assertTrue( $user->isWatched( Title::newFromText( $name ) ) );
1323  }
1324 
1325  public function testEditUnwatch() {
1326  $name = 'Help:' . ucfirst( __FUNCTION__ );
1327  $user = self::$users['sysop']->getUser();
1328  $titleObj = Title::newFromText( $name );
1329 
1330  $user->addWatch( $titleObj );
1331 
1332  $this->assertFalse( $titleObj->exists() );
1333  $this->assertTrue( $user->isWatched( $titleObj ) );
1334 
1335  $this->doApiRequestWithToken( [
1336  'action' => 'edit',
1337  'title' => $name,
1338  'text' => 'Some text',
1339  'unwatch' => '',
1340  ] );
1341 
1342  $this->assertTrue( $titleObj->exists() );
1343  $this->assertFalse( $user->isWatched( $titleObj ) );
1344  }
1345 
1346  public function testEditWithTag() {
1347  $name = 'Help:' . ucfirst( __FUNCTION__ );
1348 
1349  ChangeTags::defineTag( 'custom tag' );
1350 
1351  $revId = $this->doApiRequestWithToken( [
1352  'action' => 'edit',
1353  'title' => $name,
1354  'text' => 'Some text',
1355  'tags' => 'custom tag',
1356  ] )[0]['edit']['newrevid'];
1357 
1358  $dbw = wfGetDB( DB_MASTER );
1359  $this->assertSame( 'custom tag', $dbw->selectField(
1360  'change_tag', 'ct_tag', [ 'ct_rev_id' => $revId ], __METHOD__ ) );
1361  }
1362 
1363  public function testEditWithoutTagPermission() {
1364  $name = 'Help:' . ucfirst( __FUNCTION__ );
1365 
1366  $this->setExpectedException( ApiUsageException::class,
1367  'You do not have permission to apply change tags along with your changes.' );
1368 
1369  $this->assertFalse( Title::newFromText( $name )->exists() );
1370 
1371  ChangeTags::defineTag( 'custom tag' );
1372  $this->setMwGlobals( 'wgRevokePermissions',
1373  [ 'user' => [ 'applychangetags' => true ] ] );
1374  try {
1375  $this->doApiRequestWithToken( [
1376  'action' => 'edit',
1377  'title' => $name,
1378  'text' => 'Some text',
1379  'tags' => 'custom tag',
1380  ] );
1381  } finally {
1382  $this->assertFalse( Title::newFromText( $name )->exists() );
1383  }
1384  }
1385 
1386  public function testEditAbortedByHook() {
1387  $name = 'Help:' . ucfirst( __FUNCTION__ );
1388 
1389  $this->setExpectedException( ApiUsageException::class,
1390  'The modification you tried to make was aborted by an extension.' );
1391 
1392  $this->hideDeprecated( 'APIEditBeforeSave hook (used in ' .
1393  'hook-APIEditBeforeSave-closure)' );
1394 
1395  $this->setTemporaryHook( 'APIEditBeforeSave',
1396  function () {
1397  return false;
1398  }
1399  );
1400 
1401  try {
1402  $this->doApiRequestWithToken( [
1403  'action' => 'edit',
1404  'title' => $name,
1405  'text' => 'Some text',
1406  ] );
1407  } finally {
1408  $this->assertFalse( Title::newFromText( $name )->exists() );
1409  }
1410  }
1411 
1413  $name = 'Help:' . ucfirst( __FUNCTION__ );
1414 
1415  $this->hideDeprecated( 'APIEditBeforeSave hook (used in ' .
1416  'hook-APIEditBeforeSave-closure)' );
1417 
1418  $this->setTemporaryHook( 'APIEditBeforeSave',
1419  function ( $unused1, $unused2, &$r ) {
1420  $r['msg'] = 'Some message';
1421  return false;
1422  } );
1423 
1424  $result = $this->doApiRequestWithToken( [
1425  'action' => 'edit',
1426  'title' => $name,
1427  'text' => 'Some text',
1428  ] );
1429  Wikimedia\restoreWarnings();
1430 
1431  $this->assertSame( [ 'msg' => 'Some message', 'result' => 'Failure' ],
1432  $result[0]['edit'] );
1433 
1434  $this->assertFalse( Title::newFromText( $name )->exists() );
1435  }
1436 
1438  $name = 'Help:' . ucfirst( __FUNCTION__ );
1439 
1440  $this->setTemporaryHook( 'EditFilterMergedContent',
1441  function ( $unused1, $unused2, Status $status ) {
1442  $status->apiHookResult = [ 'msg' => 'A message for you!' ];
1443  return false;
1444  } );
1445 
1446  $res = $this->doApiRequestWithToken( [
1447  'action' => 'edit',
1448  'title' => $name,
1449  'text' => 'Some text',
1450  ] );
1451 
1452  $this->assertFalse( Title::newFromText( $name )->exists() );
1453  $this->assertSame( [ 'edit' => [ 'msg' => 'A message for you!',
1454  'result' => 'Failure' ] ], $res[0] );
1455  }
1456 
1458  $name = 'Help:' . ucfirst( __FUNCTION__ );
1459 
1460  $this->setExpectedException( ApiUsageException::class,
1461  'The modification you tried to make was aborted by an extension.' );
1462 
1463  $this->setTemporaryHook( 'EditFilterMergedContent',
1464  function () {
1465  return false;
1466  }
1467  );
1468 
1469  try {
1470  $this->doApiRequestWithToken( [
1471  'action' => 'edit',
1472  'title' => $name,
1473  'text' => 'Some text',
1474  ] );
1475  } finally {
1476  $this->assertFalse( Title::newFromText( $name )->exists() );
1477  }
1478  }
1479 
1480  public function testEditWhileBlocked() {
1481  $name = 'Help:' . ucfirst( __FUNCTION__ );
1482 
1483  $this->setExpectedException( ApiUsageException::class,
1484  'You have been blocked from editing.' );
1485 
1486  $block = new Block( [
1487  'address' => self::$users['sysop']->getUser()->getName(),
1488  'by' => self::$users['sysop']->getUser()->getId(),
1489  'reason' => 'Capriciousness',
1490  'timestamp' => '19370101000000',
1491  'expiry' => 'infinity',
1492  ] );
1493  $block->insert();
1494 
1495  try {
1496  $this->doApiRequestWithToken( [
1497  'action' => 'edit',
1498  'title' => $name,
1499  'text' => 'Some text',
1500  ] );
1501  } finally {
1502  $block->delete();
1503  self::$users['sysop']->getUser()->clearInstanceCache();
1504  }
1505  }
1506 
1507  public function testEditWhileReadOnly() {
1508  $name = 'Help:' . ucfirst( __FUNCTION__ );
1509 
1510  $this->setExpectedException( ApiUsageException::class,
1511  'The wiki is currently in read-only mode.' );
1512 
1513  $svc = \MediaWiki\MediaWikiServices::getInstance()->getReadOnlyMode();
1514  $svc->setReason( "Read-only for testing" );
1515 
1516  try {
1517  $this->doApiRequestWithToken( [
1518  'action' => 'edit',
1519  'title' => $name,
1520  'text' => 'Some text',
1521  ] );
1522  } finally {
1523  $svc->setReason( false );
1524  }
1525  }
1526 
1527  public function testCreateImageRedirectAnon() {
1528  $name = 'File:' . ucfirst( __FUNCTION__ );
1529 
1530  $this->setExpectedException( ApiUsageException::class,
1531  "Anonymous users can't create image redirects." );
1532 
1533  $this->doApiRequestWithToken( [
1534  'action' => 'edit',
1535  'title' => $name,
1536  'text' => '#REDIRECT [[File:Other file.png]]',
1537  ], null, new User() );
1538  }
1539 
1541  $name = 'File:' . ucfirst( __FUNCTION__ );
1542 
1543  $this->setExpectedException( ApiUsageException::class,
1544  "You don't have permission to create image redirects." );
1545 
1546  $this->setMwGlobals( 'wgRevokePermissions',
1547  [ 'user' => [ 'upload' => true ] ] );
1548 
1549  $this->doApiRequestWithToken( [
1550  'action' => 'edit',
1551  'title' => $name,
1552  'text' => '#REDIRECT [[File:Other file.png]]',
1553  ] );
1554  }
1555 
1556  public function testTooBigEdit() {
1557  $name = 'Help:' . ucfirst( __FUNCTION__ );
1558 
1559  $this->setExpectedException( ApiUsageException::class,
1560  'The content you supplied exceeds the article size limit of 1 kilobyte.' );
1561 
1562  $this->setMwGlobals( 'wgMaxArticleSize', 1 );
1563 
1564  $text = str_repeat( '!', 1025 );
1565 
1566  $this->doApiRequestWithToken( [
1567  'action' => 'edit',
1568  'title' => $name,
1569  'text' => $text,
1570  ] );
1571  }
1572 
1573  public function testProhibitedAnonymousEdit() {
1574  $name = 'Help:' . ucfirst( __FUNCTION__ );
1575 
1576  $this->setExpectedException( ApiUsageException::class,
1577  'The action you have requested is limited to users in the group: ' );
1578 
1579  $this->setMwGlobals( 'wgRevokePermissions', [ '*' => [ 'edit' => true ] ] );
1580 
1581  $this->doApiRequestWithToken( [
1582  'action' => 'edit',
1583  'title' => $name,
1584  'text' => 'Some text',
1585  ], null, new User() );
1586  }
1587 
1589  $name = 'Help:' . ucfirst( __FUNCTION__ );
1590 
1591  $this->setExpectedException( ApiUsageException::class,
1592  "You don't have permission to change the content model of a page." );
1593 
1594  $this->setMwGlobals( 'wgRevokePermissions',
1595  [ 'user' => [ 'editcontentmodel' => true ] ] );
1596 
1597  $this->doApiRequestWithToken( [
1598  'action' => 'edit',
1599  'title' => $name,
1600  'text' => 'Some text',
1601  'contentmodel' => 'json',
1602  ] );
1603  }
1604 }
ApiEditPageTest\testUndoToRevFromDifferentPage
testUndoToRevFromDifferentPage()
Definition: ApiEditPageTest.php:803
$user
please add to it if you re going to add events to the MediaWiki code where normally authentication against an external auth plugin would be creating a account $user
Definition: hooks.txt:247
$wgNamespaceContentModels
$wgNamespaceContentModels
Associative array mapping namespace IDs to the name of the content model pages in that namespace shou...
Definition: DefaultSettings.php:8554
Title\newFromText
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:273
ApiEditPageTest\testEditRecreate
testEditRecreate()
Definition: ApiEditPageTest.php:1287
ApiUsageException
Exception used to abort API execution with an error.
Definition: ApiUsageException.php:103
ApiEditPageTest\testAppendToNonexistentSection
testAppendToNonexistentSection()
Definition: ApiEditPageTest.php:1200
ApiEditPageTest\testCreateImageRedirectAnon
testCreateImageRedirectAnon()
Definition: ApiEditPageTest.php:1527
use
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
Definition: APACHE-LICENSE-2.0.txt:10
ApiEditPageTest\testEditConflict_newSection
testEditConflict_newSection()
Ensure that editing using section=new will prevent simple conflicts.
Definition: ApiEditPageTest.php:384
ApiEditPageTest\testEditAppend
testEditAppend( $text, $op, $append, $expected)
provideEditAppend
Definition: ApiEditPageTest.php:137
ApiEditPageTest\testEditWithTag
testEditWithTag()
Definition: ApiEditPageTest.php:1346
ApiEditPageTest\testProhibitedChangeContentModel
testProhibitedChangeContentModel()
Definition: ApiEditPageTest.php:1588
wfTimestamp
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
Definition: GlobalFunctions.php:1980
ApiEditPageTest\testEditMinor
testEditMinor()
Definition: ApiEditPageTest.php:1270
ApiEditPageTest\testMd5PrependAndAppendText
testMd5PrependAndAppendText()
Definition: ApiEditPageTest.php:891
WikiPage
Class representing a MediaWiki article and history.
Definition: WikiPage.php:37
serialize
serialize()
Definition: ApiMessage.php:184
ApiEditPageTest\testUndoToInvalidRev
testUndoToInvalidRev()
Definition: ApiEditPageTest.php:630
page
target page
Definition: All_system_messages.txt:1267
ApiEditPageTest\provideEditAppend
static provideEditAppend()
Definition: ApiEditPageTest.php:111
$res
$res
Definition: database.txt:21
User
User
Definition: All_system_messages.txt:425
ApiEditPageTest\testEditAbortedByHook
testEditAbortedByHook()
Definition: ApiEditPageTest.php:1386
$result
The index of the header message $result[1]=The index of the body text message $result[2 through n]=Parameters passed to body text message. Please note the header message cannot receive/use parameters. 'ImgAuthModifyHeaders':Executed just before a file is streamed to a user via img_auth.php, allowing headers to be modified beforehand. $title:LinkTarget object & $headers:HTTP headers(name=> value, names are case insensitive). Two headers get special handling:If-Modified-Since(value must be a valid HTTP date) and Range(must be of the form "bytes=(\d*-\d*)") will be honored when streaming the file. 'ImportHandleLogItemXMLTag':When parsing a XML tag in a log item. Return false to stop further processing of the tag $reader:XMLReader object $logInfo:Array of information 'ImportHandlePageXMLTag':When parsing a XML tag in a page. Return false to stop further processing of the tag $reader:XMLReader object & $pageInfo:Array of information 'ImportHandleRevisionXMLTag':When parsing a XML tag in a page revision. Return false to stop further processing of the tag $reader:XMLReader object $pageInfo:Array of page information $revisionInfo:Array of revision information 'ImportHandleToplevelXMLTag':When parsing a top level XML tag. Return false to stop further processing of the tag $reader:XMLReader object 'ImportHandleUnknownUser':When a user doesn 't exist locally, this hook is called to give extensions an opportunity to auto-create it. If the auto-creation is successful, return false. $name:User name 'ImportHandleUploadXMLTag':When parsing a XML tag in a file upload. Return false to stop further processing of the tag $reader:XMLReader object $revisionInfo:Array of information 'ImportLogInterwikiLink':Hook to change the interwiki link used in log entries and edit summaries for transwiki imports. & $fullInterwikiPrefix:Interwiki prefix, may contain colons. & $pageTitle:String that contains page title. 'ImportSources':Called when reading from the $wgImportSources configuration variable. Can be used to lazy-load the import sources list. & $importSources:The value of $wgImportSources. Modify as necessary. See the comment in DefaultSettings.php for the detail of how to structure this array. 'InfoAction':When building information to display on the action=info page. $context:IContextSource object & $pageInfo:Array of information 'InitializeArticleMaybeRedirect':MediaWiki check to see if title is a redirect. & $title:Title object for the current page & $request:WebRequest & $ignoreRedirect:boolean to skip redirect check & $target:Title/string of redirect target & $article:Article object 'InternalParseBeforeLinks':during Parser 's internalParse method before links but after nowiki/noinclude/includeonly/onlyinclude and other processings. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InternalParseBeforeSanitize':during Parser 's internalParse method just before the parser removes unwanted/dangerous HTML tags and after nowiki/noinclude/includeonly/onlyinclude and other processings. Ideal for syntax-extensions after template/parser function execution which respect nowiki and HTML-comments. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InterwikiLoadPrefix':When resolving if a given prefix is an interwiki or not. Return true without providing an interwiki to continue interwiki search. $prefix:interwiki prefix we are looking for. & $iwData:output array describing the interwiki with keys iw_url, iw_local, iw_trans and optionally iw_api and iw_wikiid. 'InvalidateEmailComplete':Called after a user 's email has been invalidated successfully. $user:user(object) whose email is being invalidated 'IRCLineURL':When constructing the URL to use in an IRC notification. Callee may modify $url and $query, URL will be constructed as $url . $query & $url:URL to index.php & $query:Query string $rc:RecentChange object that triggered url generation 'IsFileCacheable':Override the result of Article::isFileCacheable()(if true) & $article:article(object) being checked 'IsTrustedProxy':Override the result of IP::isTrustedProxy() & $ip:IP being check & $result:Change this value to override the result of IP::isTrustedProxy() 'IsUploadAllowedFromUrl':Override the result of UploadFromUrl::isAllowedUrl() $url:URL used to upload from & $allowed:Boolean indicating if uploading is allowed for given URL 'isValidEmailAddr':Override the result of Sanitizer::validateEmail(), for instance to return false if the domain name doesn 't match your organization. $addr:The e-mail address entered by the user & $result:Set this and return false to override the internal checks 'isValidPassword':Override the result of User::isValidPassword() $password:The password entered by the user & $result:Set this and return false to override the internal checks $user:User the password is being validated for 'Language::getMessagesFileName':$code:The language code or the language we 're looking for a messages file for & $file:The messages file path, you can override this to change the location. 'LanguageGetMagic':DEPRECATED! Use $magicWords in a file listed in $wgExtensionMessagesFiles instead. Use this to define synonyms of magic words depending of the language & $magicExtensions:associative array of magic words synonyms $lang:language code(string) 'LanguageGetNamespaces':Provide custom ordering for namespaces or remove namespaces. Do not use this hook to add namespaces. Use CanonicalNamespaces for that. & $namespaces:Array of namespaces indexed by their numbers 'LanguageGetSpecialPageAliases':DEPRECATED! Use $specialPageAliases in a file listed in $wgExtensionMessagesFiles instead. Use to define aliases of special pages names depending of the language & $specialPageAliases:associative array of magic words synonyms $lang:language code(string) 'LanguageGetTranslatedLanguageNames':Provide translated language names. & $names:array of language code=> language name $code:language of the preferred translations 'LanguageLinks':Manipulate a page 's language links. This is called in various places to allow extensions to define the effective language links for a page. $title:The page 's Title. & $links:Array with elements of the form "language:title" in the order that they will be output. & $linkFlags:Associative array mapping prefixed links to arrays of flags. Currently unused, but planned to provide support for marking individual language links in the UI, e.g. for featured articles. 'LanguageSelector':Hook to change the language selector available on a page. $out:The output page. $cssClassName:CSS class name of the language selector. 'LinkBegin':DEPRECATED! Use HtmlPageLinkRendererBegin instead. Used when generating internal and interwiki links in Linker::link(), before processing starts. Return false to skip default processing and return $ret. See documentation for Linker::link() for details on the expected meanings of parameters. $skin:the Skin object $target:the Title that the link is pointing to & $html:the contents that the< a > tag should have(raw HTML) $result
Definition: hooks.txt:1993
ApiEditPageTest\testEditWithoutTagPermission
testEditWithoutTagPermission()
Definition: ApiEditPageTest.php:1363
ApiEditPageTest\testSupportsDirectApiEditing_withContentHandlerOverride
testSupportsDirectApiEditing_withContentHandlerOverride()
Definition: ApiEditPageTest.php:493
cache
you have access to all of the normal MediaWiki so you can get a DB use the cache
Definition: maintenance.txt:55
ApiEditPageTest\testUndoAfterToHiddenRev
testUndoAfterToHiddenRev()
Tests what happens if the undo parameter is a valid revision, but undoafter is hidden (rev_deleted).
Definition: ApiEditPageTest.php:689
ApiEditPageTest
Tests for MediaWiki api.php?action=edit.
Definition: ApiEditPageTest.php:14
php
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:37
ApiEditPageTest\testMd5PrependText
testMd5PrependText()
Definition: ApiEditPageTest.php:857
ApiEditPageTest\testProhibitedAnonymousEdit
testProhibitedAnonymousEdit()
Definition: ApiEditPageTest.php:1573
ApiEditPageTest\testEditUnwatch
testEditUnwatch()
Definition: ApiEditPageTest.php:1325
ApiEditPageTest\testAppendNewSectionWithInvalidContentModel
testAppendNewSectionWithInvalidContentModel()
Definition: ApiEditPageTest.php:1079
ApiEditPageTest\setUp
setUp()
Definition: ApiEditPageTest.php:16
Status
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition: Status.php:40
MWNamespace\clearCaches
static clearCaches()
Clear internal caches.
Definition: MWNamespace.php:76
ApiEditPageTest\testUndoAfterToInvalidRev
testUndoAfterToInvalidRev()
Tests what happens if the undo parameter is a valid revision, but the undoafter parameter doesn't ref...
Definition: ApiEditPageTest.php:652
ApiEditPageTest\testEditSection
testEditSection()
Test editing of sections.
Definition: ApiEditPageTest.php:175
ApiEditPageTest\testEditConflict_bug41990
testEditConflict_bug41990()
Definition: ApiEditPageTest.php:418
ApiEditPageTest\testAppendNewSectionWithTitleAndSummary
testAppendNewSectionWithTitleAndSummary()
Definition: ApiEditPageTest.php:1139
ApiEditPageTest\testMismatchedContentFormat
testMismatchedContentFormat()
Definition: ApiEditPageTest.php:610
ApiEditPageTest\testUndoWithSwappedRevisions
testUndoWithSwappedRevisions()
Test undo when a revision with a higher id has an earlier timestamp.
Definition: ApiEditPageTest.php:722
WikiPage\factory
static factory(Title $title)
Create a WikiPage object of the appropriate class for the given title.
Definition: WikiPage.php:115
ApiEditPageTest\testEditAbortedByEditPageHookWithResult
testEditAbortedByEditPageHookWithResult()
Definition: ApiEditPageTest.php:1437
$wgContentHandlers
$wgContentHandlers
Plugins for page content model handling.
Definition: DefaultSettings.php:1010
ApiEditPageTest\testAppendWithNonTextContentHandler
testAppendWithNonTextContentHandler()
Appending/prepending is currently only supported for TextContent.
Definition: ApiEditPageTest.php:1003
ApiEditPageTest\testReversedUndoAfter
testReversedUndoAfter()
undoafter is supposed to be less than undo.
Definition: ApiEditPageTest.php:784
ApiEditPageTest\testAppendNewSectionWithTitle
testAppendNewSectionWithTitle()
Definition: ApiEditPageTest.php:1096
ApiEditPageTest\testEditWatch
testEditWatch()
Definition: ApiEditPageTest.php:1310
ApiEditPageTest\testEditWhileBlocked
testEditWhileBlocked()
Definition: ApiEditPageTest.php:1480
ApiEditPageTest\testEdit_redirect
testEdit_redirect()
Ensure we can edit through a redirect, if adding a section.
Definition: ApiEditPageTest.php:254
wfGetDB
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
Definition: GlobalFunctions.php:2812
MediaWikiTestCase\setMwGlobals
setMwGlobals( $pairs, $value=null)
Sets a global, maintaining a stashed version of the previous global to be restored in tearDown.
Definition: MediaWikiTestCase.php:678
ApiEditPageTest\testMd5Text
testMd5Text()
Definition: ApiEditPageTest.php:842
MediaWikiTestCase\hideDeprecated
hideDeprecated( $function)
Don't throw a warning if $function is deprecated and called later.
Definition: MediaWikiTestCase.php:1675
ApiEditPageTest\testEdit
testEdit()
Definition: ApiEditPageTest.php:54
ApiEditPageTest\testEditConflict
testEditConflict()
Definition: ApiEditPageTest.php:345
function
when a variable name is used in a function
Definition: design.txt:93
ApiTestCase\doApiRequestWithToken
doApiRequestWithToken(array $params, array $session=null, User $user=null, $tokenType='auto')
Convenience function to access the token parameter of doApiRequest() more succinctly.
Definition: ApiTestCase.php:170
$title
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:964
ApiEditPageTest\testIncorrectMd5Text
testIncorrectMd5Text()
Definition: ApiEditPageTest.php:909
ChangeTags\defineTag
static defineTag( $tag)
Defines a tag in the valid_tag table, without checking that the tag name is valid.
Definition: ChangeTags.php:825
global
when a variable name is used in a it is silently declared as a new masking the global
Definition: design.txt:95
WikiPage\getLatest
getLatest()
Get the page_latest field.
Definition: WikiPage.php:624
DB_MASTER
const DB_MASTER
Definition: defines.php:26
WikitextContent
Content object for wiki text pages.
Definition: WikitextContent.php:33
ApiEditPageTest\testCreateImageRedirectLoggedIn
testCreateImageRedirectLoggedIn()
Definition: ApiEditPageTest.php:1540
RevisionDeleter\createList
static createList( $typeName, IContextSource $context, Title $title, array $ids)
Instantiate the appropriate list class for a given list of IDs.
Definition: RevisionDeleter.php:83
list
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
Definition: deferred.txt:11
ApiEditPageTest\testEditWithStartTimestamp
testEditWithStartTimestamp()
Definition: ApiEditPageTest.php:1244
ContentHandler\makeContent
static makeContent( $text, Title $title=null, $modelId=null, $format=null)
Convenience function for creating a Content object from a given textual representation.
Definition: ContentHandler.php:129
ApiEditPageTest\testIncorrectMd5AppendText
testIncorrectMd5AppendText()
Definition: ApiEditPageTest.php:938
ApiEditPageTest\testUnsupportedContentFormat
testUnsupportedContentFormat()
Definition: ApiEditPageTest.php:592
ApiTestCase
Definition: ApiTestCase.php:5
ApiEditPageTest\testAppendNewSection
testAppendNewSection()
Definition: ApiEditPageTest.php:1061
EDIT_UPDATE
const EDIT_UPDATE
Definition: Defines.php:163
ApiTestCase\editPage
editPage( $pageName, $text, $summary='', $defaultNs=NS_MAIN)
Edits or creates a page/revision.
Definition: ApiTestCase.php:52
ApiEditPageTest\testUndoAfterContentModelChange
testUndoAfterContentModelChange()
This test verifies that after changing the content model of a page, undoing that edit via the API wil...
Definition: ApiEditPageTest.php:526
ApiEditPageTest\testEditAbortedByHookWithCustomOutput
testEditAbortedByHookWithCustomOutput()
Definition: ApiEditPageTest.php:1412
ApiEditPageTest\testEdit_redirectText
testEdit_redirectText()
Ensure we cannot edit through a redirect, if attempting to overwrite content.
Definition: ApiEditPageTest.php:300
Revision\RAW
const RAW
Definition: Revision.php:57
$status
Status::newGood()` to allow deletion, and then `return false` from the hook function. Ensure you consume the 'ChangeTagAfterDelete' hook to carry out custom deletion actions. $tag:name of the tag $user:user initiating the action & $status:Status object. See above. 'ChangeTagsListActive':Allows you to nominate which of the tags your extension uses are in active use. & $tags:list of all active tags. Append to this array. 'ChangeTagsAfterUpdateTags':Called after tags have been updated with the ChangeTags::updateTags function. Params:$addedTags:tags effectively added in the update $removedTags:tags effectively removed in the update $prevTags:tags that were present prior to the update $rc_id:recentchanges table id $rev_id:revision table id $log_id:logging table id $params:tag params $rc:RecentChange being tagged when the tagging accompanies the action or null $user:User who performed the tagging when the tagging is subsequent to the action or null 'ChangeTagsAllowedAdd':Called when checking if a user can add tags to a change. & $allowedTags:List of all the tags the user is allowed to add. Any tags the user wants to add( $addTags) that are not in this array will cause it to fail. You may add or remove tags to this array as required. $addTags:List of tags user intends to add. $user:User who is adding the tags. 'ChangeUserGroups':Called before user groups are changed. $performer:The User who will perform the change $user:The User whose groups will be changed & $add:The groups that will be added & $remove:The groups that will be removed 'Collation::factory':Called if $wgCategoryCollation is an unknown collation. $collationName:Name of the collation in question & $collationObject:Null. Replace with a subclass of the Collation class that implements the collation given in $collationName. 'ConfirmEmailComplete':Called after a user 's email has been confirmed successfully. $user:user(object) whose email is being confirmed 'ContentAlterParserOutput':Modify parser output for a given content object. Called by Content::getParserOutput after parsing has finished. Can be used for changes that depend on the result of the parsing but have to be done before LinksUpdate is called(such as adding tracking categories based on the rendered HTML). $content:The Content to render $title:Title of the page, as context $parserOutput:ParserOutput to manipulate 'ContentGetParserOutput':Customize parser output for a given content object, called by AbstractContent::getParserOutput. May be used to override the normal model-specific rendering of page content. $content:The Content to render $title:Title of the page, as context $revId:The revision ID, as context $options:ParserOptions for rendering. To avoid confusing the parser cache, the output can only depend on parameters provided to this hook function, not on global state. $generateHtml:boolean, indicating whether full HTML should be generated. If false, generation of HTML may be skipped, but other information should still be present in the ParserOutput object. & $output:ParserOutput, to manipulate or replace 'ContentHandlerDefaultModelFor':Called when the default content model is determined for a given title. May be used to assign a different model for that title. $title:the Title in question & $model:the model name. Use with CONTENT_MODEL_XXX constants. 'ContentHandlerForModelID':Called when a ContentHandler is requested for a given content model name, but no entry for that model exists in $wgContentHandlers. Note:if your extension implements additional models via this hook, please use GetContentModels hook to make them known to core. $modeName:the requested content model name & $handler:set this to a ContentHandler object, if desired. 'ContentModelCanBeUsedOn':Called to determine whether that content model can be used on a given page. This is especially useful to prevent some content models to be used in some special location. $contentModel:ID of the content model in question $title:the Title in question. & $ok:Output parameter, whether it is OK to use $contentModel on $title. Handler functions that modify $ok should generally return false to prevent further hooks from further modifying $ok. 'ContribsPager::getQueryInfo':Before the contributions query is about to run & $pager:Pager object for contributions & $queryInfo:The query for the contribs Pager 'ContribsPager::reallyDoQuery':Called before really executing the query for My Contributions & $data:an array of results of all contribs queries $pager:The ContribsPager object hooked into $offset:Index offset, inclusive $limit:Exact query limit $descending:Query direction, false for ascending, true for descending 'ContributionsLineEnding':Called before a contributions HTML line is finished $page:SpecialPage object for contributions & $ret:the HTML line $row:the DB row for this line & $classes:the classes to add to the surrounding< li > & $attribs:associative array of other HTML attributes for the< li > element. Currently only data attributes reserved to MediaWiki are allowed(see Sanitizer::isReservedDataAttribute). 'ContributionsToolLinks':Change tool links above Special:Contributions $id:User identifier $title:User page title & $tools:Array of tool links $specialPage:SpecialPage instance for context and services. Can be either SpecialContributions or DeletedContributionsPage. Extensions should type hint against a generic SpecialPage though. 'ConvertContent':Called by AbstractContent::convert when a conversion to another content model is requested. Handler functions that modify $result should generally return false to disable further attempts at conversion. $content:The Content object to be converted. $toModel:The ID of the content model to convert to. $lossy:boolean indicating whether lossy conversion is allowed. & $result:Output parameter, in case the handler function wants to provide a converted Content object. Note that $result->getContentModel() must return $toModel. 'CustomEditor':When invoking the page editor Return true to allow the normal editor to be used, or false if implementing a custom editor, e.g. for a special namespace, etc. $article:Article being edited $user:User performing the edit 'DatabaseOraclePostInit':Called after initialising an Oracle database $db:the DatabaseOracle object 'DeletedContribsPager::reallyDoQuery':Called before really executing the query for Special:DeletedContributions Similar to ContribsPager::reallyDoQuery & $data:an array of results of all contribs queries $pager:The DeletedContribsPager object hooked into $offset:Index offset, inclusive $limit:Exact query limit $descending:Query direction, false for ascending, true for descending 'DeletedContributionsLineEnding':Called before a DeletedContributions HTML line is finished. Similar to ContributionsLineEnding $page:SpecialPage object for DeletedContributions & $ret:the HTML line $row:the DB row for this line & $classes:the classes to add to the surrounding< li > & $attribs:associative array of other HTML attributes for the< li > element. Currently only data attributes reserved to MediaWiki are allowed(see Sanitizer::isReservedDataAttribute). 'DeleteUnknownPreferences':Called by the cleanupPreferences.php maintenance script to build a WHERE clause with which to delete preferences that are not known about. This hook is used by extensions that have dynamically-named preferences that should not be deleted in the usual cleanup process. For example, the Gadgets extension creates preferences prefixed with 'gadget-', and so anything with that prefix is excluded from the deletion. &where:An array that will be passed as the $cond parameter to IDatabase::select() to determine what will be deleted from the user_properties table. $db:The IDatabase object, useful for accessing $db->buildLike() etc. 'DifferenceEngineAfterLoadNewText':called in DifferenceEngine::loadNewText() after the new revision 's content has been loaded into the class member variable $differenceEngine->mNewContent but before returning true from this function. $differenceEngine:DifferenceEngine object 'DifferenceEngineLoadTextAfterNewContentIsLoaded':called in DifferenceEngine::loadText() after the new revision 's content has been loaded into the class member variable $differenceEngine->mNewContent but before checking if the variable 's value is null. This hook can be used to inject content into said class member variable. $differenceEngine:DifferenceEngine object 'DifferenceEngineMarkPatrolledLink':Allows extensions to change the "mark as patrolled" link which is shown both on the diff header as well as on the bottom of a page, usually wrapped in a span element which has class="patrollink". $differenceEngine:DifferenceEngine object & $markAsPatrolledLink:The "mark as patrolled" link HTML(string) $rcid:Recent change ID(rc_id) for this change(int) 'DifferenceEngineMarkPatrolledRCID':Allows extensions to possibly change the rcid parameter. For example the rcid might be set to zero due to the user being the same as the performer of the change but an extension might still want to show it under certain conditions. & $rcid:rc_id(int) of the change or 0 $differenceEngine:DifferenceEngine object $change:RecentChange object $user:User object representing the current user 'DifferenceEngineNewHeader':Allows extensions to change the $newHeader variable, which contains information about the new revision, such as the revision 's author, whether the revision was marked as a minor edit or not, etc. $differenceEngine:DifferenceEngine object & $newHeader:The string containing the various #mw-diff-otitle[1-5] divs, which include things like revision author info, revision comment, RevisionDelete link and more $formattedRevisionTools:Array containing revision tools, some of which may have been injected with the DiffRevisionTools hook $nextlink:String containing the link to the next revision(if any) $status
Definition: hooks.txt:1255
ApiEditPageTest\testEditMalformedSection
testEditMalformedSection()
Definition: ApiEditPageTest.php:1222
RequestContext\getMain
static getMain()
Get the RequestContext object associated with the main request.
Definition: RequestContext.php:434
EDIT_NEW
const EDIT_NEW
Definition: Defines.php:162
ApiEditPageTest\testAppendNewSectionWithSummary
testAppendNewSectionWithSummary()
Definition: ApiEditPageTest.php:1117
Title
Represents a title within MediaWiki.
Definition: Title.php:39
$name
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:302
ApiEditPageTest\testTooBigEdit
testTooBigEdit()
Definition: ApiEditPageTest.php:1556
ApiEditPageTest\testAppendInMediaWikiNamespace
testAppendInMediaWikiNamespace()
Definition: ApiEditPageTest.php:1025
ApiEditPageTest\testCorrectContentFormat
testCorrectContentFormat()
Definition: ApiEditPageTest.php:578
ApiEditPageTest\testMd5AppendText
testMd5AppendText()
Definition: ApiEditPageTest.php:874
ApiEditPageTest\testEditAbortedByEditPageHookWithNoResult
testEditAbortedByEditPageHookWithNoResult()
Definition: ApiEditPageTest.php:1457
ApiEditPageTest\testAppendToSection
testAppendToSection()
Definition: ApiEditPageTest.php:1161
ApiEditPageTest\testAppendInMediaWikiNamespaceWithSerializationError
testAppendInMediaWikiNamespaceWithSerializationError()
Definition: ApiEditPageTest.php:1039
Block
Definition: Block.php:27
ApiEditPageTest\testNoCreate
testNoCreate()
Definition: ApiEditPageTest.php:978
ApiEditPageTest\testEditNewSection
testEditNewSection()
Test action=edit&section=new Run it twice so we test adding a new section on a page that doesn't exis...
Definition: ApiEditPageTest.php:214
ApiEditPageTest\testUndoWithConflicts
testUndoWithConflicts()
Definition: ApiEditPageTest.php:757
class
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:56
ApiEditPageTest\testCheckDirectApiEditingDisallowed_forNonTextContent
testCheckDirectApiEditingDisallowed_forNonTextContent()
Definition: ApiEditPageTest.php:479
MediaWikiTestCase\setTemporaryHook
setTemporaryHook( $hookName, $handler)
Create a temporary hook handler which will be reset by tearDown.
Definition: MediaWikiTestCase.php:2014
ApiEditPageTest\testUndoAfterToRevFromDifferentPage
testUndoAfterToRevFromDifferentPage()
Definition: ApiEditPageTest.php:822
ApiEditPageTest\testEditWhileReadOnly
testEditWhileReadOnly()
Definition: ApiEditPageTest.php:1507
ApiEditPageTest\testIncorrectMd5PrependText
testIncorrectMd5PrependText()
Definition: ApiEditPageTest.php:923
ApiEditPageTest\tearDown
tearDown()
Definition: ApiEditPageTest.php:45
ApiEditPageTest\forceRevisionDate
forceRevisionDate(WikiPage $page, $timestamp)
Definition: ApiEditPageTest.php:469
ApiEditPageTest\testCreateOnly
testCreateOnly()
Definition: ApiEditPageTest.php:953
$wgExtraNamespaces
$wgExtraNamespaces
Additional namespaces.
Definition: DefaultSettings.php:3888
Revision\DELETED_TEXT
const DELETED_TEXT
Definition: Revision.php:47
$e
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException' returning false will NOT prevent logging $e
Definition: hooks.txt:2171
WikiPage\clear
clear()
Clear the object.
Definition: WikiPage.php:244
$wgContLang
this class mediates it Skin Encapsulates a look and feel for the wiki All of the functions that render HTML and make choices about how to render it are here and are called from various other places when and is meant to be subclassed with other skins that may override some of its functions The User object contains a reference to a and so rather than having a global skin object we just rely on the global User and get the skin with $wgUser and also has some character encoding functions and other locale stuff The current user interface language is instantiated as and the content language as $wgContLang
Definition: design.txt:57
ApiEditPageTest\testAppendToFirstSection
testAppendToFirstSection()
Definition: ApiEditPageTest.php:1181