It adds MediaWiki\Content namespace to WikitextContent and two classes related. Change-Id: Ib74e4c5b3edac6aa0e35d3b2093ce1d0b794cb6d
1787 lines
52 KiB
PHP
1787 lines
52 KiB
PHP
<?php
|
|
|
|
namespace MediaWiki\Tests\Api;
|
|
|
|
use ApiUsageException;
|
|
use IDBAccessObject;
|
|
use MediaWiki\Block\DatabaseBlock;
|
|
use MediaWiki\CommentStore\CommentStoreComment;
|
|
use MediaWiki\Content\JavaScriptContent;
|
|
use MediaWiki\Content\WikitextContent;
|
|
use MediaWiki\Context\RequestContext;
|
|
use MediaWiki\MainConfigNames;
|
|
use MediaWiki\Revision\RevisionRecord;
|
|
use MediaWiki\Status\Status;
|
|
use MediaWiki\Tests\User\TempUser\TempUserTestTrait;
|
|
use MediaWiki\Title\Title;
|
|
use MediaWiki\Title\TitleValue;
|
|
use MediaWiki\User\User;
|
|
use MediaWiki\Utils\MWTimestamp;
|
|
use RevisionDeleter;
|
|
use WikiPage;
|
|
|
|
/**
|
|
* Tests for MediaWiki api.php?action=edit.
|
|
*
|
|
* @author Daniel Kinzler
|
|
*
|
|
* @group API
|
|
* @group Database
|
|
* @group medium
|
|
*
|
|
* @covers \ApiEditPage
|
|
*/
|
|
class ApiEditPageTest extends ApiTestCase {
|
|
|
|
use TempUserTestTrait;
|
|
|
|
protected function setUp(): void {
|
|
parent::setUp();
|
|
|
|
$this->overrideConfigValues( [
|
|
MainConfigNames::ExtraNamespaces => [
|
|
12312 => 'Dummy',
|
|
12313 => 'Dummy_talk',
|
|
12314 => 'DummyNonText',
|
|
12315 => 'DummyNonText_talk',
|
|
],
|
|
MainConfigNames::NamespaceContentModels => [
|
|
12312 => 'testing',
|
|
12314 => 'testing-nontext',
|
|
],
|
|
MainConfigNames::WatchlistExpiry => true,
|
|
MainConfigNames::WatchlistExpiryMaxDuration => '6 months',
|
|
] );
|
|
$this->mergeMwGlobalArrayValue( 'wgContentHandlers', [
|
|
'testing' => 'DummyContentHandlerForTesting',
|
|
'testing-nontext' => 'DummyNonTextContentHandler',
|
|
'testing-serialize-error' => 'DummySerializeErrorContentHandler',
|
|
] );
|
|
}
|
|
|
|
public function testEdit() {
|
|
$name = 'Help:ApiEditPageTest_testEdit'; // assume Help namespace to default to wikitext
|
|
|
|
// -- test new page --------------------------------------------
|
|
$apiResult = $this->doApiRequestWithToken( [
|
|
'action' => 'edit',
|
|
'title' => $name,
|
|
'text' => 'some text',
|
|
] );
|
|
$apiResult = $apiResult[0];
|
|
|
|
// Validate API result data
|
|
$this->assertArrayHasKey( 'edit', $apiResult );
|
|
$this->assertArrayHasKey( 'result', $apiResult['edit'] );
|
|
$this->assertSame( 'Success', $apiResult['edit']['result'] );
|
|
|
|
$this->assertArrayHasKey( 'new', $apiResult['edit'] );
|
|
$this->assertArrayNotHasKey( 'nochange', $apiResult['edit'] );
|
|
|
|
$this->assertArrayHasKey( 'pageid', $apiResult['edit'] );
|
|
|
|
// -- test existing page, no change ----------------------------
|
|
$data = $this->doApiRequestWithToken( [
|
|
'action' => 'edit',
|
|
'title' => $name,
|
|
'text' => 'some text',
|
|
] );
|
|
|
|
$this->assertSame( 'Success', $data[0]['edit']['result'] );
|
|
|
|
$this->assertArrayNotHasKey( 'new', $data[0]['edit'] );
|
|
$this->assertArrayHasKey( 'nochange', $data[0]['edit'] );
|
|
|
|
// -- test existing page, with change --------------------------
|
|
$data = $this->doApiRequestWithToken( [
|
|
'action' => 'edit',
|
|
'title' => $name,
|
|
'text' => 'different text'
|
|
] );
|
|
|
|
$this->assertSame( 'Success', $data[0]['edit']['result'] );
|
|
|
|
$this->assertArrayNotHasKey( 'new', $data[0]['edit'] );
|
|
$this->assertArrayNotHasKey( 'nochange', $data[0]['edit'] );
|
|
|
|
$this->assertArrayHasKey( 'oldrevid', $data[0]['edit'] );
|
|
$this->assertArrayHasKey( 'newrevid', $data[0]['edit'] );
|
|
$this->assertNotEquals(
|
|
$data[0]['edit']['newrevid'],
|
|
$data[0]['edit']['oldrevid'],
|
|
"revision id should change after edit"
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @return array
|
|
*/
|
|
public static function provideEditAppend() {
|
|
return [
|
|
[ # 0: append
|
|
'foo', 'append', 'bar', "foobar"
|
|
],
|
|
[ # 1: prepend
|
|
'foo', 'prepend', 'bar', "barfoo"
|
|
],
|
|
[ # 2: append to empty page
|
|
'', 'append', 'foo', "foo"
|
|
],
|
|
[ # 3: prepend to empty page
|
|
'', 'prepend', 'foo', "foo"
|
|
],
|
|
[ # 4: append to non-existing page
|
|
null, 'append', 'foo', "foo"
|
|
],
|
|
[ # 5: prepend to non-existing page
|
|
null, 'prepend', 'foo', "foo"
|
|
],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @dataProvider provideEditAppend
|
|
*/
|
|
public function testEditAppend( $text, $op, $append, $expected ) {
|
|
static $count = 0;
|
|
$count++;
|
|
|
|
// assume NS_HELP defaults to wikitext
|
|
$title = Title::makeTitle( NS_HELP, "ApiEditPageTest_testEditAppend_$count" );
|
|
|
|
// -- create page (or not) -----------------------------------------
|
|
if ( $text !== null ) {
|
|
[ $re ] = $this->doApiRequestWithToken( [
|
|
'action' => 'edit',
|
|
'title' => $title->getPrefixedText(),
|
|
'text' => $text, ] );
|
|
|
|
$this->assertSame( 'Success', $re['edit']['result'] );
|
|
}
|
|
|
|
// -- try append/prepend --------------------------------------------
|
|
[ $re ] = $this->doApiRequestWithToken( [
|
|
'action' => 'edit',
|
|
'title' => $title->getPrefixedText(),
|
|
$op . 'text' => $append, ] );
|
|
|
|
$this->assertSame( 'Success', $re['edit']['result'] );
|
|
|
|
// -- validate -----------------------------------------------------
|
|
$page = $this->getServiceContainer()->getWikiPageFactory()->newFromTitle( $title );
|
|
$content = $page->getContent();
|
|
$this->assertNotNull( $content, 'Page should have been created' );
|
|
|
|
$text = $content->getText();
|
|
|
|
$this->assertSame( $expected, $text );
|
|
}
|
|
|
|
/**
|
|
* Test editing of sections
|
|
*/
|
|
public function testEditSection() {
|
|
$title = Title::makeTitle( NS_HELP, 'ApiEditPageTest_testEditSection' );
|
|
$wikiPageFactory = $this->getServiceContainer()->getWikiPageFactory();
|
|
$page = $wikiPageFactory->newFromTitle( $title );
|
|
$text = "==section 1==\ncontent 1\n==section 2==\ncontent2";
|
|
// Preload the page with some text
|
|
$page->doUserEditContent(
|
|
$page->getContentHandler()->unserializeContent( $text ),
|
|
$this->getTestSysop()->getAuthority(),
|
|
'summary'
|
|
);
|
|
|
|
[ $re ] = $this->doApiRequestWithToken( [
|
|
'action' => 'edit',
|
|
'title' => $title->getPrefixedText(),
|
|
'section' => '1',
|
|
'text' => "==section 1==\nnew content 1",
|
|
] );
|
|
$this->assertSame( 'Success', $re['edit']['result'] );
|
|
$newtext = $wikiPageFactory->newFromTitle( $title )
|
|
->getContent( RevisionRecord::RAW )
|
|
->getText();
|
|
$this->assertSame( "==section 1==\nnew content 1\n\n==section 2==\ncontent2", $newtext );
|
|
|
|
// Test that we raise a 'nosuchsection' error
|
|
try {
|
|
$this->doApiRequestWithToken( [
|
|
'action' => 'edit',
|
|
'title' => $title->getPrefixedText(),
|
|
'section' => '9999',
|
|
'text' => 'text',
|
|
] );
|
|
$this->fail( "Should have raised an ApiUsageException" );
|
|
} catch ( ApiUsageException $e ) {
|
|
$this->assertApiErrorCode( 'nosuchsection', $e );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test action=edit§ion=new
|
|
* Run it twice so we test adding a new section on a
|
|
* page that doesn't exist (T54830) and one that
|
|
* does exist
|
|
*/
|
|
public function testEditNewSection() {
|
|
$title = Title::makeTitle( NS_HELP, 'ApiEditPageTest_testEditNewSection' );
|
|
$wikiPageFactory = $this->getServiceContainer()->getWikiPageFactory();
|
|
|
|
// Test on a page that does not already exist
|
|
$this->assertFalse( $title->exists() );
|
|
[ $re ] = $this->doApiRequestWithToken( [
|
|
'action' => 'edit',
|
|
'title' => $title->getPrefixedText(),
|
|
'section' => 'new',
|
|
'text' => 'test',
|
|
'summary' => 'header',
|
|
] );
|
|
|
|
$this->assertSame( 'Success', $re['edit']['result'] );
|
|
// Check the page text is correct
|
|
$text = $wikiPageFactory->newFromTitle( $title )
|
|
->getContent( RevisionRecord::RAW )
|
|
->getText();
|
|
$this->assertSame( "== header ==\n\ntest", $text );
|
|
|
|
// Now on one that does
|
|
$this->assertTrue( $title->exists( IDBAccessObject::READ_LATEST ) );
|
|
[ $re2 ] = $this->doApiRequestWithToken( [
|
|
'action' => 'edit',
|
|
'title' => $title->getPrefixedText(),
|
|
'section' => 'new',
|
|
'text' => 'test',
|
|
'summary' => 'header',
|
|
] );
|
|
|
|
$this->assertSame( 'Success', $re2['edit']['result'] );
|
|
$text = $wikiPageFactory->newFromTitle( $title )
|
|
->getContent( RevisionRecord::RAW )
|
|
->getText();
|
|
$this->assertSame( "== header ==\n\ntest\n\n== header ==\n\ntest", $text );
|
|
}
|
|
|
|
/**
|
|
* Test action=edit§ion=new with different combinations of summary and sectiontitle.
|
|
*
|
|
* @dataProvider provideEditNewSectionSummarySectiontitle
|
|
*/
|
|
public function testEditNewSectionSummarySectiontitle(
|
|
$sectiontitle,
|
|
$summary,
|
|
$expectedText,
|
|
$expectedSummary
|
|
) {
|
|
static $count = 0;
|
|
$count++;
|
|
$title = Title::makeTitle( NS_HELP, 'ApiEditPageTest_testEditNewSectionSummarySectiontitle' . $count );
|
|
|
|
// Test edit 1 (new page)
|
|
$this->doApiRequestWithToken( [
|
|
'action' => 'edit',
|
|
'title' => $title->getPrefixedText(),
|
|
'section' => 'new',
|
|
'text' => 'text',
|
|
'sectiontitle' => $sectiontitle,
|
|
'summary' => $summary,
|
|
] );
|
|
|
|
$wikiPageFactory = $this->getServiceContainer()->getWikiPageFactory();
|
|
$wikiPage = $wikiPageFactory->newFromTitle( $title );
|
|
|
|
// Check the page text is correct
|
|
$savedText = $wikiPage->getContent( RevisionRecord::RAW )->getText();
|
|
$this->assertSame( $expectedText, $savedText, 'Correct text saved (new page)' );
|
|
|
|
// Check that the edit summary is correct
|
|
// (when not provided or empty, there is an autogenerated summary for page creation)
|
|
$savedSummary = $wikiPage->getRevisionRecord()->getComment( RevisionRecord::RAW )->text;
|
|
$expectedSummaryNew = $expectedSummary ?: wfMessage( 'autosumm-new' )->rawParams( $expectedText )
|
|
->inContentLanguage()->text();
|
|
$this->assertSame( $expectedSummaryNew, $savedSummary, 'Correct summary saved (new page)' );
|
|
|
|
// Clear the page
|
|
$this->editPage( $wikiPage, '' );
|
|
|
|
// Test edit 2 (existing page)
|
|
$this->doApiRequestWithToken( [
|
|
'action' => 'edit',
|
|
'title' => $title->getPrefixedText(),
|
|
'section' => 'new',
|
|
'text' => 'text',
|
|
'sectiontitle' => $sectiontitle,
|
|
'summary' => $summary,
|
|
] );
|
|
|
|
$wikiPage = $wikiPageFactory->newFromTitle( $title );
|
|
|
|
// Check the page text is correct
|
|
$savedText = $wikiPage->getContent( RevisionRecord::RAW )->getText();
|
|
$this->assertSame( $expectedText, $savedText, 'Correct text saved (existing page)' );
|
|
|
|
// Check that the edit summary is correct
|
|
$savedSummary = $wikiPage->getRevisionRecord()->getComment( RevisionRecord::RAW )->text;
|
|
$this->assertSame( $expectedSummary, $savedSummary, 'Correct summary saved (existing page)' );
|
|
}
|
|
|
|
public static function provideEditNewSectionSummarySectiontitle() {
|
|
$sectiontitleCases = [
|
|
'unset' => null,
|
|
'empty' => '',
|
|
'set' => 'sectiontitle',
|
|
];
|
|
$summaryCases = [
|
|
'unset' => null,
|
|
'empty' => '',
|
|
'set' => 'summary',
|
|
];
|
|
|
|
$expectedTexts = [
|
|
"text",
|
|
"text",
|
|
"== summary ==\n\ntext",
|
|
"text",
|
|
"text",
|
|
"text",
|
|
"== sectiontitle ==\n\ntext",
|
|
"== sectiontitle ==\n\ntext",
|
|
"== sectiontitle ==\n\ntext",
|
|
];
|
|
|
|
$expectedSummaries = [
|
|
'',
|
|
'',
|
|
'/* summary */ new section',
|
|
'',
|
|
'',
|
|
'summary',
|
|
'/* sectiontitle */ new section',
|
|
'/* sectiontitle */ new section',
|
|
'summary',
|
|
];
|
|
|
|
$i = 0;
|
|
foreach ( $sectiontitleCases as $sectiontitleDesc => $sectiontitle ) {
|
|
foreach ( $summaryCases as $summaryDesc => $summary ) {
|
|
$message = "sectiontitle $sectiontitleDesc, summary $summaryDesc";
|
|
yield $message => [
|
|
$sectiontitle,
|
|
$summary,
|
|
$expectedTexts[$i],
|
|
$expectedSummaries[$i],
|
|
];
|
|
$i++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Ensure we can edit through a redirect, if adding a section
|
|
*/
|
|
public function testEdit_redirect() {
|
|
static $count = 0;
|
|
$count++;
|
|
|
|
// assume NS_HELP defaults to wikitext
|
|
$title = Title::makeTitle( NS_HELP, "ApiEditPageTest_testEdit_redirect_$count" );
|
|
$wikiPageFactory = $this->getServiceContainer()->getWikiPageFactory();
|
|
$page = $this->getExistingTestPage( $title );
|
|
$this->forceRevisionDate( $page, '20120101000000' );
|
|
|
|
$rtitle = Title::makeTitle( NS_HELP, "ApiEditPageTest_testEdit_redirect_r$count" );
|
|
$rpage = $wikiPageFactory->newFromTitle( $rtitle );
|
|
|
|
$baseTime = $page->getRevisionRecord()->getTimestamp();
|
|
|
|
// base edit for redirect
|
|
$rpage->doUserEditContent(
|
|
new WikitextContent( "#REDIRECT [[{$title->getPrefixedText()}]]" ),
|
|
$this->getTestSysop()->getUser(),
|
|
"testing 1",
|
|
EDIT_NEW
|
|
);
|
|
$this->forceRevisionDate( $rpage, '20120101000000' );
|
|
|
|
// conflicting edit to redirect
|
|
$rpage->doUserEditContent(
|
|
new WikitextContent( "#REDIRECT [[{$title->getPrefixedText()}]]\n\n[[Category:Test]]" ),
|
|
$this->getTestUser()->getUser(),
|
|
"testing 2",
|
|
EDIT_UPDATE
|
|
);
|
|
$this->forceRevisionDate( $rpage, '20120101020202' );
|
|
|
|
// try to save edit, following the redirect
|
|
[ $re, , ] = $this->doApiRequestWithToken( [
|
|
'action' => 'edit',
|
|
'title' => $rtitle->getPrefixedText(),
|
|
'text' => 'nix bar!',
|
|
'basetimestamp' => $baseTime,
|
|
'section' => 'new',
|
|
'redirect' => true,
|
|
] );
|
|
|
|
$this->assertSame( 'Success', $re['edit']['result'],
|
|
"no problems expected when following redirect" );
|
|
}
|
|
|
|
/**
|
|
* Ensure we cannot edit through a redirect, if attempting to overwrite content
|
|
*/
|
|
public function testEdit_redirectText() {
|
|
static $count = 0;
|
|
$count++;
|
|
|
|
// assume NS_HELP defaults to wikitext
|
|
$title = Title::makeTitle( NS_HELP, "ApiEditPageTest_testEdit_redirectText_$count" );
|
|
$wikiPageFactory = $this->getServiceContainer()->getWikiPageFactory();
|
|
$page = $this->getExistingTestPage( $title );
|
|
$this->forceRevisionDate( $page, '20120101000000' );
|
|
$baseTime = $page->getRevisionRecord()->getTimestamp();
|
|
|
|
$rtitle = Title::makeTitle( NS_HELP, "ApiEditPageTest_testEdit_redirectText_r$count" );
|
|
$rpage = $wikiPageFactory->newFromTitle( $rtitle );
|
|
|
|
// base edit for redirect
|
|
$rpage->doUserEditContent(
|
|
new WikitextContent( "#REDIRECT [[{$title->getPrefixedText()}]]" ),
|
|
$this->getTestSysop()->getUser(),
|
|
"testing 1",
|
|
EDIT_NEW
|
|
);
|
|
$this->forceRevisionDate( $rpage, '20120101000000' );
|
|
|
|
// conflicting edit to redirect
|
|
$rpage->doUserEditContent(
|
|
new WikitextContent( "#REDIRECT [[{$title->getPrefixedText()}]]\n\n[[Category:Test]]" ),
|
|
$this->getTestUser()->getUser(),
|
|
"testing 2",
|
|
EDIT_UPDATE
|
|
);
|
|
$this->forceRevisionDate( $rpage, '20120101020202' );
|
|
|
|
// try to save edit, following the redirect but without creating a section
|
|
try {
|
|
$this->doApiRequestWithToken( [
|
|
'action' => 'edit',
|
|
'title' => $rtitle->getPrefixedText(),
|
|
'text' => 'nix bar!',
|
|
'basetimestamp' => $baseTime,
|
|
'redirect' => true,
|
|
] );
|
|
|
|
$this->fail( 'redirect-appendonly error expected' );
|
|
} catch ( ApiUsageException $ex ) {
|
|
$this->assertApiErrorCode( 'redirect-appendonly', $ex );
|
|
}
|
|
}
|
|
|
|
public function testEditConflict_revid() {
|
|
static $count = 0;
|
|
$count++;
|
|
|
|
// assume NS_HELP defaults to wikitext
|
|
$title = Title::makeTitle( NS_HELP, "ApiEditPageTest_testEditConflict_$count" );
|
|
|
|
$page = $this->getServiceContainer()->getWikiPageFactory()->newFromTitle( $title );
|
|
|
|
// base edit
|
|
$page->doUserEditContent(
|
|
new WikitextContent( "Foo" ),
|
|
$this->getTestSysop()->getUser(),
|
|
"testing 1",
|
|
EDIT_NEW
|
|
);
|
|
$this->forceRevisionDate( $page, '20120101000000' );
|
|
$baseId = $page->getRevisionRecord()->getId();
|
|
|
|
// conflicting edit
|
|
$page->doUserEditContent(
|
|
new WikitextContent( "Foo bar" ),
|
|
$this->getTestUser()->getUser(),
|
|
"testing 2",
|
|
EDIT_UPDATE
|
|
);
|
|
$this->forceRevisionDate( $page, '20120101020202' );
|
|
|
|
// try to save edit, expect conflict
|
|
try {
|
|
$this->doApiRequestWithToken( [
|
|
'action' => 'edit',
|
|
'title' => $title->getPrefixedText(),
|
|
'text' => 'nix bar!',
|
|
'baserevid' => $baseId,
|
|
], null, $this->getTestSysop()->getUser() );
|
|
|
|
$this->fail( 'edit conflict expected' );
|
|
} catch ( ApiUsageException $ex ) {
|
|
$this->assertApiErrorCode( 'editconflict', $ex );
|
|
}
|
|
}
|
|
|
|
public function testEditConflict_timestamp() {
|
|
static $count = 0;
|
|
$count++;
|
|
|
|
// assume NS_HELP defaults to wikitext
|
|
$title = Title::makeTitle( NS_HELP, "ApiEditPageTest_testEditConflict_$count" );
|
|
|
|
$page = $this->getServiceContainer()->getWikiPageFactory()->newFromTitle( $title );
|
|
|
|
// base edit
|
|
$page->doUserEditContent(
|
|
new WikitextContent( "Foo" ),
|
|
$this->getTestSysop()->getUser(),
|
|
"testing 1",
|
|
EDIT_NEW
|
|
);
|
|
$this->forceRevisionDate( $page, '20120101000000' );
|
|
$baseTime = $page->getRevisionRecord()->getTimestamp();
|
|
|
|
// conflicting edit
|
|
$page->doUserEditContent(
|
|
new WikitextContent( "Foo bar" ),
|
|
$this->getTestUser()->getUser(),
|
|
"testing 2",
|
|
EDIT_UPDATE
|
|
);
|
|
$this->forceRevisionDate( $page, '20120101020202' );
|
|
|
|
// try to save edit, expect conflict
|
|
try {
|
|
$this->doApiRequestWithToken( [
|
|
'action' => 'edit',
|
|
'title' => $title->getPrefixedText(),
|
|
'text' => 'nix bar!',
|
|
'basetimestamp' => $baseTime,
|
|
] );
|
|
|
|
$this->fail( 'edit conflict expected' );
|
|
} catch ( ApiUsageException $ex ) {
|
|
$this->assertApiErrorCode( 'editconflict', $ex );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Ensure that editing using section=new will prevent simple conflicts
|
|
*/
|
|
public function testEditConflict_newSection() {
|
|
static $count = 0;
|
|
$count++;
|
|
|
|
// assume NS_HELP defaults to wikitext
|
|
$title = Title::makeTitle( NS_HELP, "ApiEditPageTest_testEditConflict_newSection_$count" );
|
|
|
|
$page = $this->getServiceContainer()->getWikiPageFactory()->newFromTitle( $title );
|
|
|
|
// base edit
|
|
$page->doUserEditContent(
|
|
new WikitextContent( "Foo" ),
|
|
$this->getTestSysop()->getUser(),
|
|
"testing 1",
|
|
EDIT_NEW
|
|
);
|
|
$this->forceRevisionDate( $page, '20120101000000' );
|
|
$baseTime = $page->getRevisionRecord()->getTimestamp();
|
|
|
|
// conflicting edit
|
|
$page->doUserEditContent(
|
|
new WikitextContent( "Foo bar" ),
|
|
$this->getTestUser()->getUser(),
|
|
"testing 2",
|
|
EDIT_UPDATE
|
|
);
|
|
$this->forceRevisionDate( $page, '20120101020202' );
|
|
|
|
// try to save edit, expect no conflict
|
|
[ $re, , ] = $this->doApiRequestWithToken( [
|
|
'action' => 'edit',
|
|
'title' => $title->getPrefixedText(),
|
|
'text' => 'nix bar!',
|
|
'basetimestamp' => $baseTime,
|
|
'section' => 'new',
|
|
] );
|
|
|
|
$this->assertSame( 'Success', $re['edit']['result'],
|
|
"no edit conflict expected here" );
|
|
}
|
|
|
|
public function testEditConflict_T43990() {
|
|
static $count = 0;
|
|
$count++;
|
|
|
|
/*
|
|
* T43990: if the target page has a newer revision than the redirect, then editing the
|
|
* redirect while specifying 'redirect' and *not* specifying 'basetimestamp' erroneously
|
|
* caused an edit conflict to be detected.
|
|
*/
|
|
|
|
// assume NS_HELP defaults to wikitext
|
|
$title = Title::makeTitle( NS_HELP, "ApiEditPageTest_testEditConflict_redirect_T43990_$count" );
|
|
$wikiPageFactory = $this->getServiceContainer()->getWikiPageFactory();
|
|
$page = $this->getExistingTestPage( $title );
|
|
$this->forceRevisionDate( $page, '20120101000000' );
|
|
|
|
$rtitle = Title::makeTitle( NS_HELP, "ApiEditPageTest_testEditConflict_redirect_T43990_r$count" );
|
|
$rpage = $wikiPageFactory->newFromTitle( $rtitle );
|
|
|
|
// base edit for redirect
|
|
$rpage->doUserEditContent(
|
|
new WikitextContent( "#REDIRECT [[{$title->getPrefixedText()}]]" ),
|
|
$this->getTestSysop()->getUser(),
|
|
"testing 1",
|
|
EDIT_NEW
|
|
);
|
|
$this->forceRevisionDate( $rpage, '20120101000000' );
|
|
|
|
// new edit to content
|
|
$page->doUserEditContent(
|
|
new WikitextContent( "Foo bar" ),
|
|
$this->getTestUser()->getUser(),
|
|
"testing 2",
|
|
EDIT_UPDATE
|
|
);
|
|
$this->forceRevisionDate( $rpage, '20120101020202' );
|
|
|
|
// try to save edit; should work, following the redirect.
|
|
[ $re, , ] = $this->doApiRequestWithToken( [
|
|
'action' => 'edit',
|
|
'title' => $rtitle->getPrefixedText(),
|
|
'text' => 'nix bar!',
|
|
'section' => 'new',
|
|
'redirect' => true,
|
|
] );
|
|
|
|
$this->assertSame( 'Success', $re['edit']['result'],
|
|
"no edit conflict expected here" );
|
|
}
|
|
|
|
/**
|
|
* @param WikiPage $page
|
|
* @param string|int $timestamp
|
|
*/
|
|
protected function forceRevisionDate( WikiPage $page, $timestamp ) {
|
|
$dbw = $this->getDb();
|
|
|
|
$dbw->newUpdateQueryBuilder()
|
|
->update( 'revision' )
|
|
->set( [ 'rev_timestamp' => $dbw->timestamp( $timestamp ) ] )
|
|
->where( [ 'rev_id' => $page->getLatest() ] )
|
|
->caller( __METHOD__ )->execute();
|
|
|
|
$page->clear();
|
|
}
|
|
|
|
public function testCheckDirectApiEditingDisallowed_forNonTextContent() {
|
|
$this->expectApiErrorCode( 'no-direct-editing' );
|
|
|
|
$this->doApiRequestWithToken( [
|
|
'action' => 'edit',
|
|
'title' => 'Dummy:ApiEditPageTest_nonTextPageEdit',
|
|
'text' => '{"animals":["kittens!"]}'
|
|
] );
|
|
}
|
|
|
|
public function testSupportsDirectApiEditing_withContentHandlerOverride() {
|
|
$name = 'DummyNonText:ApiEditPageTest_testNonTextEdit';
|
|
$data = 'some bla bla text';
|
|
|
|
$result = $this->doApiRequestWithToken( [
|
|
'action' => 'edit',
|
|
'title' => $name,
|
|
'text' => $data,
|
|
] );
|
|
|
|
$apiResult = $result[0];
|
|
|
|
// Validate API result data
|
|
$this->assertArrayHasKey( 'edit', $apiResult );
|
|
$this->assertArrayHasKey( 'result', $apiResult['edit'] );
|
|
$this->assertSame( 'Success', $apiResult['edit']['result'] );
|
|
|
|
$this->assertArrayHasKey( 'new', $apiResult['edit'] );
|
|
$this->assertArrayNotHasKey( 'nochange', $apiResult['edit'] );
|
|
|
|
$this->assertArrayHasKey( 'pageid', $apiResult['edit'] );
|
|
|
|
// validate resulting revision
|
|
$page = $this->getServiceContainer()->getWikiPageFactory()->newFromTitle( Title::newFromText( $name ) );
|
|
$this->assertSame( "testing-nontext", $page->getContentModel() );
|
|
$this->assertSame( $data, $page->getContent()->serialize() );
|
|
}
|
|
|
|
/**
|
|
* This test verifies that after changing the content model
|
|
* of a page, undoing that edit via the API will also
|
|
* undo the content model change.
|
|
*/
|
|
public function testUndoAfterContentModelChange() {
|
|
$name = 'Help:' . __FUNCTION__;
|
|
$sysop = $this->getTestSysop()->getUser();
|
|
$otherUser = $this->getTestUser()->getUser();
|
|
|
|
$apiResult = $this->doApiRequestWithToken( [
|
|
'action' => 'edit',
|
|
'title' => $name,
|
|
'text' => 'some text',
|
|
], null, $sysop )[0];
|
|
|
|
// Check success
|
|
$this->assertArrayHasKey( 'edit', $apiResult );
|
|
$this->assertArrayHasKey( 'result', $apiResult['edit'] );
|
|
$this->assertSame( 'Success', $apiResult['edit']['result'] );
|
|
$this->assertArrayHasKey( 'contentmodel', $apiResult['edit'] );
|
|
// Content model is wikitext
|
|
$this->assertSame( 'wikitext', $apiResult['edit']['contentmodel'] );
|
|
|
|
// Convert the page to JSON
|
|
$apiResult = $this->doApiRequestWithToken( [
|
|
'action' => 'edit',
|
|
'title' => $name,
|
|
'text' => '{}',
|
|
'contentmodel' => 'json',
|
|
], null, $otherUser )[0];
|
|
|
|
// Check success
|
|
$this->assertArrayHasKey( 'edit', $apiResult );
|
|
$this->assertArrayHasKey( 'result', $apiResult['edit'] );
|
|
$this->assertSame( 'Success', $apiResult['edit']['result'] );
|
|
$this->assertArrayHasKey( 'contentmodel', $apiResult['edit'] );
|
|
$this->assertSame( 'json', $apiResult['edit']['contentmodel'] );
|
|
|
|
$apiResult = $this->doApiRequestWithToken( [
|
|
'action' => 'edit',
|
|
'title' => $name,
|
|
'undo' => $apiResult['edit']['newrevid']
|
|
], null, $sysop )[0];
|
|
|
|
// Check success
|
|
$this->assertArrayHasKey( 'edit', $apiResult );
|
|
$this->assertArrayHasKey( 'result', $apiResult['edit'] );
|
|
$this->assertSame( 'Success', $apiResult['edit']['result'] );
|
|
$this->assertArrayHasKey( 'contentmodel', $apiResult['edit'] );
|
|
// Check that the contentmodel is back to wikitext now.
|
|
$this->assertSame( 'wikitext', $apiResult['edit']['contentmodel'] );
|
|
}
|
|
|
|
// The tests below are mostly not commented because they do exactly what
|
|
// you'd expect from the name.
|
|
|
|
public function testCorrectContentFormat() {
|
|
$title = Title::makeTitle( NS_HELP, 'TestCorrectContentFormat' );
|
|
|
|
$this->doApiRequestWithToken( [
|
|
'action' => 'edit',
|
|
'title' => $title->getPrefixedText(),
|
|
'text' => 'some text',
|
|
'contentmodel' => 'wikitext',
|
|
'contentformat' => 'text/x-wiki',
|
|
] );
|
|
|
|
$this->assertTrue( $title->exists( IDBAccessObject::READ_LATEST ) );
|
|
}
|
|
|
|
public function testUnsupportedContentFormat() {
|
|
$title = Title::makeTitle( NS_HELP, 'TestUnsupportedContentFormat' );
|
|
|
|
$this->expectApiErrorCode( 'badvalue' );
|
|
|
|
try {
|
|
$this->doApiRequestWithToken( [
|
|
'action' => 'edit',
|
|
'title' => $title->getPrefixedText(),
|
|
'text' => 'some text',
|
|
'contentformat' => 'nonexistent format',
|
|
] );
|
|
} finally {
|
|
$this->assertFalse( $title->exists( IDBAccessObject::READ_LATEST ) );
|
|
}
|
|
}
|
|
|
|
public function testMismatchedContentFormat() {
|
|
$title = Title::makeTitle( NS_HELP, 'TestMismatchedContentFormat' );
|
|
|
|
$this->expectApiErrorCode( 'badformat' );
|
|
|
|
try {
|
|
$this->doApiRequestWithToken( [
|
|
'action' => 'edit',
|
|
'title' => $title->getPrefixedText(),
|
|
'text' => 'some text',
|
|
'contentmodel' => 'wikitext',
|
|
'contentformat' => 'text/plain',
|
|
] );
|
|
} finally {
|
|
$this->assertFalse( $title->exists( IDBAccessObject::READ_LATEST ) );
|
|
}
|
|
}
|
|
|
|
public function testUndoToInvalidRev() {
|
|
$title = Title::makeTitle( NS_HELP, 'TestUndoToInvalidRev' );
|
|
|
|
$revId = $this->editPage( $title, 'Some text' )->getNewRevision()
|
|
->getId();
|
|
$revId++;
|
|
|
|
$this->expectApiErrorCode( 'nosuchrevid' );
|
|
|
|
$this->doApiRequestWithToken( [
|
|
'action' => 'edit',
|
|
'title' => $title->getPrefixedText(),
|
|
'undo' => $revId,
|
|
] );
|
|
}
|
|
|
|
/**
|
|
* Tests what happens if the undo parameter is a valid revision, but
|
|
* the undoafter parameter doesn't refer to a revision that exists in the
|
|
* database.
|
|
*/
|
|
public function testUndoAfterToInvalidRev() {
|
|
// We can't just pick a large number for undoafter (as in
|
|
// testUndoToInvalidRev above), because then MediaWiki will helpfully
|
|
// assume we switched around undo and undoafter and we'll test the code
|
|
// path for undo being invalid, not undoafter. So instead we delete
|
|
// the revision from the database. In real life this case could come
|
|
// up if a revision number was skipped, e.g., if two transactions try
|
|
// to insert new revision rows at once and the first one to succeed
|
|
// gets rolled back.
|
|
$page = $this->getServiceContainer()->getWikiPageFactory()
|
|
->newFromLinkTarget( new TitleValue( NS_HELP, 'TestUndoAfterToInvalidRev' ) );
|
|
|
|
$revId1 = $this->editPage( $page, '1' )->getNewRevision()->getId();
|
|
$revId2 = $this->editPage( $page, '2' )->getNewRevision()->getId();
|
|
$revId3 = $this->editPage( $page, '3' )->getNewRevision()->getId();
|
|
|
|
// Make the middle revision disappear
|
|
$dbw = $this->getDb();
|
|
$dbw->newDeleteQueryBuilder()
|
|
->deleteFrom( 'revision' )
|
|
->where( [ 'rev_id' => $revId2 ] )
|
|
->caller( __METHOD__ )->execute();
|
|
$dbw->newUpdateQueryBuilder()
|
|
->update( 'revision' )
|
|
->set( [ 'rev_parent_id' => $revId1 ] )
|
|
->where( [ 'rev_id' => $revId3 ] )
|
|
->caller( __METHOD__ )->execute();
|
|
|
|
$this->expectApiErrorCode( 'nosuchrevid' );
|
|
|
|
$this->doApiRequestWithToken( [
|
|
'action' => 'edit',
|
|
'title' => $page->getTitle()->getPrefixedText(),
|
|
'undo' => $revId3,
|
|
'undoafter' => $revId2,
|
|
] );
|
|
}
|
|
|
|
/**
|
|
* Tests what happens if the undo parameter is a valid revision, but
|
|
* undoafter is hidden (rev_deleted).
|
|
*/
|
|
public function testUndoAfterToHiddenRev() {
|
|
$page = $this->getServiceContainer()->getWikiPageFactory()
|
|
->newFromLinkTarget( new TitleValue( NS_HELP, 'TestUndoAfterToHiddenRev' ) );
|
|
$titleObj = $page->getTitle();
|
|
|
|
$this->editPage( $page, '0' );
|
|
|
|
$revId1 = $this->editPage( $page, '1' )->getNewRevision()->getId();
|
|
|
|
$revId2 = $this->editPage( $page, '2' )->getNewRevision()->getId();
|
|
|
|
// Hide the middle revision
|
|
$list = RevisionDeleter::createList( 'revision',
|
|
RequestContext::getMain(), $titleObj, [ $revId1 ] );
|
|
// Set a user for modifying the visibility, this is needed because
|
|
// setVisibility generates a log, which cannot be an anonymous user actor
|
|
// when temporary accounts are enabled.
|
|
RequestContext::getMain()->setUser( $this->getTestUser()->getUser() );
|
|
$list->setVisibility( [
|
|
'value' => [ RevisionRecord::DELETED_TEXT => 1 ],
|
|
'comment' => 'Bye-bye',
|
|
] );
|
|
|
|
$this->expectApiErrorCode( 'nosuchrevid' );
|
|
|
|
$this->doApiRequestWithToken( [
|
|
'action' => 'edit',
|
|
'title' => $titleObj->getPrefixedText(),
|
|
'undo' => $revId2,
|
|
'undoafter' => $revId1,
|
|
] );
|
|
}
|
|
|
|
/**
|
|
* Test undo when a revision with a higher id has an earlier timestamp.
|
|
* This can happen if importing an old revision.
|
|
*/
|
|
public function testUndoWithSwappedRevisions() {
|
|
$this->markTestSkippedIfNoDiff3();
|
|
|
|
$page = $this->getServiceContainer()->getWikiPageFactory()
|
|
->newFromLinkTarget( new TitleValue( NS_HELP, 'TestUndoWithSwappedRevisions' ) );
|
|
$this->editPage( $page, '0' );
|
|
|
|
$revId2 = $this->editPage( $page, '2' )->getNewRevision()->getId();
|
|
|
|
$revId1 = $this->editPage( $page, '1' )->getNewRevision()->getId();
|
|
|
|
// Now monkey with the timestamp
|
|
$dbw = $this->getDb();
|
|
$dbw->newUpdateQueryBuilder()
|
|
->update( 'revision' )
|
|
->set( [ 'rev_timestamp' => $dbw->timestamp( time() - 86400 ) ] )
|
|
->where( [ 'rev_id' => $revId1 ] )
|
|
->caller( __METHOD__ )->execute();
|
|
|
|
$this->doApiRequestWithToken( [
|
|
'action' => 'edit',
|
|
'title' => $page->getTitle()->getPrefixedText(),
|
|
'undo' => $revId2,
|
|
'undoafter' => $revId1,
|
|
] );
|
|
|
|
$page->loadPageData( IDBAccessObject::READ_LATEST );
|
|
$this->assertSame( '1', $page->getContent()->getText() );
|
|
}
|
|
|
|
public function testUndoWithConflicts() {
|
|
$this->expectApiErrorCode( 'undofailure' );
|
|
|
|
$page = $this->getServiceContainer()->getWikiPageFactory()
|
|
->newFromLinkTarget( new TitleValue( NS_HELP, 'TestUndoWithConflicts' ) );
|
|
$this->editPage( $page, '1' );
|
|
|
|
$revId = $this->editPage( $page, '2' )->getNewRevision()->getId();
|
|
|
|
$this->editPage( $page, '3' );
|
|
|
|
$this->doApiRequestWithToken( [
|
|
'action' => 'edit',
|
|
'title' => $page->getTitle()->getPrefixedText(),
|
|
'undo' => $revId,
|
|
] );
|
|
|
|
$page->loadPageData( IDBAccessObject::READ_LATEST );
|
|
$this->assertSame( '3', $page->getContent()->getText() );
|
|
}
|
|
|
|
public function testReversedUndoAfter() {
|
|
$this->markTestSkippedIfNoDiff3();
|
|
|
|
$page = $this->getServiceContainer()->getWikiPageFactory()
|
|
->newFromLinkTarget( new TitleValue( NS_HELP, 'TestReversedUndoAfter' ) );
|
|
$this->editPage( $page, '0' );
|
|
$revId1 = $this->editPage( $page, '1' )->getNewRevision()->getId();
|
|
$revId2 = $this->editPage( $page, '2' )->getNewRevision()->getId();
|
|
|
|
$this->doApiRequestWithToken( [
|
|
'action' => 'edit',
|
|
'title' => $page->getTitle()->getPrefixedText(),
|
|
'undo' => $revId1,
|
|
'undoafter' => $revId2,
|
|
] );
|
|
|
|
$page->loadPageData( IDBAccessObject::READ_LATEST );
|
|
$this->assertSame( '2', $page->getContent()->getText() );
|
|
}
|
|
|
|
public function testUndoToRevFromDifferentPage() {
|
|
$title1 = Title::makeTitle( NS_HELP, 'TestUndoToRevFromDifferentPage-1' );
|
|
$this->editPage( $title1, 'Some text' );
|
|
$revId = $this->editPage( $title1, 'Some more text' )
|
|
->getNewRevision()->getId();
|
|
|
|
$title2 = Title::makeTitle( NS_HELP, 'TestUndoToRevFromDifferentPage-2' );
|
|
$this->editPage( $title2, 'Some text' );
|
|
|
|
$this->expectApiErrorCode( 'revwrongpage' );
|
|
|
|
$this->doApiRequestWithToken( [
|
|
'action' => 'edit',
|
|
'title' => $title2->getPrefixedText(),
|
|
'undo' => $revId,
|
|
] );
|
|
}
|
|
|
|
public function testUndoAfterToRevFromDifferentPage() {
|
|
$title1 = Title::makeTitle( NS_HELP, 'TestUndoAfterToRevFromDifferentPage-1' );
|
|
$revId1 = $this->editPage( $title1, 'Some text' )
|
|
->getNewRevision()->getId();
|
|
|
|
$title2 = Title::makeTitle( NS_HELP, 'TestUndoAfterToRevFromDifferentPage-2' );
|
|
$revId2 = $this->editPage( $title2, 'Some text' )
|
|
->getNewRevision()->getId();
|
|
|
|
$this->expectApiErrorCode( 'revwrongpage' );
|
|
|
|
$this->doApiRequestWithToken( [
|
|
'action' => 'edit',
|
|
'title' => $title2->getPrefixedText(),
|
|
'undo' => $revId2,
|
|
'undoafter' => $revId1,
|
|
] );
|
|
}
|
|
|
|
public function testMd5Text() {
|
|
$title = Title::makeTitle( NS_HELP, 'TestMd5Text' );
|
|
|
|
$this->assertFalse( $title->exists( IDBAccessObject::READ_LATEST ) );
|
|
|
|
$this->doApiRequestWithToken( [
|
|
'action' => 'edit',
|
|
'title' => $title->getPrefixedText(),
|
|
'text' => 'Some text',
|
|
'md5' => md5( 'Some text' ),
|
|
] );
|
|
|
|
$this->assertTrue( $title->exists( IDBAccessObject::READ_LATEST ) );
|
|
}
|
|
|
|
public function testMd5PrependText() {
|
|
$title = Title::makeTitle( NS_HELP, 'TestMd5PrependText' );
|
|
|
|
$this->editPage( $title, 'Some text' );
|
|
|
|
$this->doApiRequestWithToken( [
|
|
'action' => 'edit',
|
|
'title' => $title->getPrefixedText(),
|
|
'prependtext' => 'Alert: ',
|
|
'md5' => md5( 'Alert: ' ),
|
|
] );
|
|
|
|
$text = $this->getServiceContainer()->getWikiPageFactory()->newFromTitle( $title )
|
|
->getContent()->getText();
|
|
$this->assertSame( 'Alert: Some text', $text );
|
|
}
|
|
|
|
public function testMd5AppendText() {
|
|
$title = Title::makeTitle( NS_HELP, 'TestMd5AppendText' );
|
|
|
|
$this->editPage( $title, 'Some text' );
|
|
|
|
$this->doApiRequestWithToken( [
|
|
'action' => 'edit',
|
|
'title' => $title->getPrefixedText(),
|
|
'appendtext' => ' is nice',
|
|
'md5' => md5( ' is nice' ),
|
|
] );
|
|
|
|
$text = $this->getServiceContainer()->getWikiPageFactory()->newFromTitle( $title )
|
|
->getContent()->getText();
|
|
$this->assertSame( 'Some text is nice', $text );
|
|
}
|
|
|
|
public function testMd5PrependAndAppendText() {
|
|
$title = Title::makeTitle( NS_HELP, 'TestMd5PrependAndAppendText' );
|
|
|
|
$this->editPage( $title, 'Some text' );
|
|
|
|
$this->doApiRequestWithToken( [
|
|
'action' => 'edit',
|
|
'title' => $title->getPrefixedText(),
|
|
'prependtext' => 'Alert: ',
|
|
'appendtext' => ' is nice',
|
|
'md5' => md5( 'Alert: is nice' ),
|
|
] );
|
|
|
|
$text = $this->getServiceContainer()->getWikiPageFactory()->newFromTitle( $title )
|
|
->getContent()->getText();
|
|
$this->assertSame( 'Alert: Some text is nice', $text );
|
|
}
|
|
|
|
public function testIncorrectMd5Text() {
|
|
$name = 'Help:' . ucfirst( __FUNCTION__ );
|
|
|
|
$this->expectApiErrorCode( 'badmd5' );
|
|
|
|
$this->doApiRequestWithToken( [
|
|
'action' => 'edit',
|
|
'title' => $name,
|
|
'text' => 'Some text',
|
|
'md5' => md5( '' ),
|
|
] );
|
|
}
|
|
|
|
public function testIncorrectMd5PrependText() {
|
|
$name = 'Help:' . ucfirst( __FUNCTION__ );
|
|
|
|
$this->expectApiErrorCode( 'badmd5' );
|
|
|
|
$this->doApiRequestWithToken( [
|
|
'action' => 'edit',
|
|
'title' => $name,
|
|
'prependtext' => 'Some ',
|
|
'appendtext' => 'text',
|
|
'md5' => md5( 'Some ' ),
|
|
] );
|
|
}
|
|
|
|
public function testIncorrectMd5AppendText() {
|
|
$name = 'Help:' . ucfirst( __FUNCTION__ );
|
|
|
|
$this->expectApiErrorCode( 'badmd5' );
|
|
|
|
$this->doApiRequestWithToken( [
|
|
'action' => 'edit',
|
|
'title' => $name,
|
|
'prependtext' => 'Some ',
|
|
'appendtext' => 'text',
|
|
'md5' => md5( 'text' ),
|
|
] );
|
|
}
|
|
|
|
public function testCreateOnly() {
|
|
$title = Title::makeTitle( NS_HELP, 'TestCreateOnly' );
|
|
|
|
$this->expectApiErrorCode( 'articleexists' );
|
|
|
|
$this->editPage( $title, 'Some text' );
|
|
$this->assertTrue( $title->exists( IDBAccessObject::READ_LATEST ) );
|
|
|
|
try {
|
|
$this->doApiRequestWithToken( [
|
|
'action' => 'edit',
|
|
'title' => $title->getPrefixedText(),
|
|
'text' => 'Some more text',
|
|
'createonly' => '',
|
|
] );
|
|
} finally {
|
|
// Validate that content was not changed
|
|
$text = $this->getServiceContainer()->getWikiPageFactory()->newFromTitle( $title )
|
|
->getContent()->getText();
|
|
|
|
$this->assertSame( 'Some text', $text );
|
|
}
|
|
}
|
|
|
|
public function testNoCreate() {
|
|
$title = Title::makeTitle( NS_HELP, 'TestNoCreate' );
|
|
|
|
$this->expectApiErrorCode( 'missingtitle' );
|
|
|
|
$this->assertFalse( $title->exists( IDBAccessObject::READ_LATEST ) );
|
|
|
|
try {
|
|
$this->doApiRequestWithToken( [
|
|
'action' => 'edit',
|
|
'title' => $title->getPrefixedText(),
|
|
'text' => 'Some text',
|
|
'nocreate' => '',
|
|
] );
|
|
} finally {
|
|
$this->assertFalse( $title->exists( IDBAccessObject::READ_LATEST ) );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Appending/prepending is currently only supported for TextContent. We
|
|
* test this right now, and when support is added this test should be
|
|
* replaced by tests that the support is correct.
|
|
*/
|
|
public function testAppendWithNonTextContentHandler() {
|
|
$name = 'MediaWiki:' . ucfirst( __FUNCTION__ );
|
|
|
|
$this->expectApiErrorCode( 'appendnotsupported' );
|
|
|
|
$this->setTemporaryHook( 'ContentHandlerDefaultModelFor',
|
|
static function ( Title $title, &$model ) use ( $name ) {
|
|
if ( $title->getPrefixedText() === $name ) {
|
|
$model = 'testing-nontext';
|
|
}
|
|
return true;
|
|
}
|
|
);
|
|
|
|
$this->doApiRequestWithToken( [
|
|
'action' => 'edit',
|
|
'title' => $name,
|
|
'appendtext' => 'Some text',
|
|
] );
|
|
}
|
|
|
|
public function testAppendInMediaWikiNamespace() {
|
|
$title = Title::makeTitle( NS_MEDIAWIKI, 'TestAppendInMediaWikiNamespace' );
|
|
|
|
$this->assertFalse( $title->exists( IDBAccessObject::READ_LATEST ) );
|
|
|
|
$this->doApiRequestWithToken( [
|
|
'action' => 'edit',
|
|
'title' => $title->getPrefixedText(),
|
|
'appendtext' => 'Some text',
|
|
] );
|
|
|
|
$this->assertTrue( $title->exists( IDBAccessObject::READ_LATEST ) );
|
|
}
|
|
|
|
public function testAppendInMediaWikiNamespaceWithSerializationError() {
|
|
$name = 'MediaWiki:' . ucfirst( __FUNCTION__ );
|
|
|
|
$this->expectApiErrorCode( 'parseerror' );
|
|
|
|
$this->setTemporaryHook( 'ContentHandlerDefaultModelFor',
|
|
static function ( Title $title, &$model ) use ( $name ) {
|
|
if ( $title->getPrefixedText() === $name ) {
|
|
$model = 'testing-serialize-error';
|
|
}
|
|
return true;
|
|
}
|
|
);
|
|
|
|
$this->doApiRequestWithToken( [
|
|
'action' => 'edit',
|
|
'title' => $name,
|
|
'appendtext' => 'Some text',
|
|
] );
|
|
}
|
|
|
|
public function testAppendNewSection() {
|
|
$title = Title::makeTitle( NS_HELP, 'TestAppendNewSection' );
|
|
|
|
$this->editPage( $title, 'Initial content' );
|
|
|
|
$this->doApiRequestWithToken( [
|
|
'action' => 'edit',
|
|
'title' => $title->getPrefixedText(),
|
|
'appendtext' => '== New section ==',
|
|
'section' => 'new',
|
|
] );
|
|
|
|
$text = $this->getServiceContainer()->getWikiPageFactory()->newFromTitle( $title )
|
|
->getContent()->getText();
|
|
|
|
$this->assertSame( "Initial content\n\n== New section ==", $text );
|
|
}
|
|
|
|
public function testAppendNewSectionWithInvalidContentModel() {
|
|
$title = Title::makeTitle( NS_HELP, 'TestAppendNewSectionWithInvalidContentModel' );
|
|
|
|
$this->expectApiErrorCode( 'sectionsnotsupported' );
|
|
|
|
$this->editPage( $title, 'Initial content' );
|
|
|
|
$this->doApiRequestWithToken( [
|
|
'action' => 'edit',
|
|
'title' => $title->getPrefixedText(),
|
|
'appendtext' => '== New section ==',
|
|
'section' => 'new',
|
|
'contentmodel' => 'text',
|
|
] );
|
|
}
|
|
|
|
public function testAppendNewSectionWithTitle() {
|
|
$title = Title::makeTitle( NS_HELP, 'TestAppendNewSectionWithTitle' );
|
|
|
|
$this->editPage( $title, 'Initial content' );
|
|
|
|
$this->doApiRequestWithToken( [
|
|
'action' => 'edit',
|
|
'title' => $title->getPrefixedText(),
|
|
'sectiontitle' => 'My section',
|
|
'appendtext' => 'More content',
|
|
'section' => 'new',
|
|
] );
|
|
|
|
$page = $this->getServiceContainer()->getWikiPageFactory()->newFromTitle( $title );
|
|
|
|
$this->assertSame( "Initial content\n\n== My section ==\n\nMore content",
|
|
$page->getContent()->getText() );
|
|
$comment = $page->getRevisionRecord()->getComment();
|
|
$this->assertInstanceOf( CommentStoreComment::class, $comment );
|
|
$this->assertSame( '/* My section */ new section', $comment->text );
|
|
}
|
|
|
|
public function testAppendNewSectionWithSummary() {
|
|
$title = Title::makeTitle( NS_HELP, 'TestAppendNewSectionWithSummary' );
|
|
|
|
$this->editPage( $title, 'Initial content' );
|
|
|
|
$this->doApiRequestWithToken( [
|
|
'action' => 'edit',
|
|
'title' => $title->getPrefixedText(),
|
|
'appendtext' => 'More content',
|
|
'section' => 'new',
|
|
'summary' => 'Add new section',
|
|
] );
|
|
|
|
$page = $this->getServiceContainer()->getWikiPageFactory()->newFromTitle( $title );
|
|
|
|
$this->assertSame( "Initial content\n\n== Add new section ==\n\nMore content",
|
|
$page->getContent()->getText() );
|
|
// EditPage actually assumes the summary is the section name here
|
|
$comment = $page->getRevisionRecord()->getComment();
|
|
$this->assertInstanceOf( CommentStoreComment::class, $comment );
|
|
$this->assertSame( '/* Add new section */ new section', $comment->text );
|
|
}
|
|
|
|
public function testAppendNewSectionWithTitleAndSummary() {
|
|
$title = Title::makeTitle( NS_HELP, 'TestAppendNewSectionWithTitleAndSummary' );
|
|
|
|
$this->editPage( $title, 'Initial content' );
|
|
|
|
$this->doApiRequestWithToken( [
|
|
'action' => 'edit',
|
|
'title' => $title->getPrefixedText(),
|
|
'sectiontitle' => 'My section',
|
|
'appendtext' => 'More content',
|
|
'section' => 'new',
|
|
'summary' => 'Add new section',
|
|
] );
|
|
|
|
$page = $this->getServiceContainer()->getWikiPageFactory()->newFromTitle( $title );
|
|
|
|
$this->assertSame( "Initial content\n\n== My section ==\n\nMore content",
|
|
$page->getContent()->getText() );
|
|
$comment = $page->getRevisionRecord()->getComment();
|
|
$this->assertInstanceOf( CommentStoreComment::class, $comment );
|
|
$this->assertSame( 'Add new section', $comment->text );
|
|
}
|
|
|
|
public function testAppendToSection() {
|
|
$title = Title::makeTitle( NS_HELP, 'TestAppendToSection' );
|
|
|
|
$this->editPage( $title, "== Section 1 ==\n\nContent\n\n" .
|
|
"== Section 2 ==\n\nFascinating!" );
|
|
|
|
$this->doApiRequestWithToken( [
|
|
'action' => 'edit',
|
|
'title' => $title->getPrefixedText(),
|
|
'appendtext' => ' and more content',
|
|
'section' => '1',
|
|
] );
|
|
|
|
$text = $this->getServiceContainer()->getWikiPageFactory()->newFromTitle( $title )
|
|
->getContent()->getText();
|
|
|
|
$this->assertSame( "== Section 1 ==\n\nContent and more content\n\n" .
|
|
"== Section 2 ==\n\nFascinating!", $text );
|
|
}
|
|
|
|
public function testAppendToFirstSection() {
|
|
$title = Title::makeTitle( NS_HELP, 'TestAppendToFirstSection' );
|
|
|
|
$this->editPage( $title, "Content\n\n== Section 1 ==\n\nFascinating!" );
|
|
|
|
$this->doApiRequestWithToken( [
|
|
'action' => 'edit',
|
|
'title' => $title->getPrefixedText(),
|
|
'appendtext' => ' and more content',
|
|
'section' => '0',
|
|
] );
|
|
|
|
$text = $this->getServiceContainer()->getWikiPageFactory()->newFromTitle( $title )
|
|
->getContent()->getText();
|
|
|
|
$this->assertSame( "Content and more content\n\n== Section 1 ==\n\n" .
|
|
"Fascinating!", $text );
|
|
}
|
|
|
|
public function testAppendToNonexistentSection() {
|
|
$title = Title::makeTitle( NS_HELP, 'TestAppendToNonexistentSection' );
|
|
|
|
$this->expectApiErrorCode( 'nosuchsection' );
|
|
|
|
$this->editPage( $title, 'Content' );
|
|
|
|
try {
|
|
$this->doApiRequestWithToken( [
|
|
'action' => 'edit',
|
|
'title' => $title->getPrefixedText(),
|
|
'appendtext' => ' and more content',
|
|
'section' => '1',
|
|
] );
|
|
} finally {
|
|
$text = $this->getServiceContainer()->getWikiPageFactory()->newFromTitle( $title )
|
|
->getContent()->getText();
|
|
|
|
$this->assertSame( 'Content', $text );
|
|
}
|
|
}
|
|
|
|
public function testEditMalformedSection() {
|
|
$title = Title::makeTitle( NS_HELP, 'TestEditMalformedSection' );
|
|
|
|
$this->expectApiErrorCode( 'invalidsection' );
|
|
$this->editPage( $title, 'Content' );
|
|
|
|
try {
|
|
$this->doApiRequestWithToken( [
|
|
'action' => 'edit',
|
|
'title' => $title->getPrefixedText(),
|
|
'text' => 'Different content',
|
|
'section' => 'It is unlikely that this is valid',
|
|
] );
|
|
} finally {
|
|
$text = $this->getServiceContainer()->getWikiPageFactory()->newFromTitle( $title )
|
|
->getContent()->getText();
|
|
|
|
$this->assertSame( 'Content', $text );
|
|
}
|
|
}
|
|
|
|
public function testEditWithStartTimestamp() {
|
|
$title = Title::makeTitle( NS_HELP, 'TestEditWithStartTimestamp' );
|
|
$this->expectApiErrorCode( 'pagedeleted' );
|
|
|
|
$startTime = MWTimestamp::convert( TS_MW, time() - 1 );
|
|
|
|
$this->editPage( $title, 'Some text' );
|
|
|
|
$pageObj = $this->getServiceContainer()->getWikiPageFactory()->newFromTitle( $title );
|
|
$this->deletePage( $pageObj );
|
|
|
|
$this->assertFalse( $pageObj->exists() );
|
|
|
|
try {
|
|
$this->doApiRequestWithToken( [
|
|
'action' => 'edit',
|
|
'title' => $title->getPrefixedText(),
|
|
'text' => 'Different text',
|
|
'starttimestamp' => $startTime,
|
|
] );
|
|
} finally {
|
|
$this->assertFalse( $pageObj->exists() );
|
|
}
|
|
}
|
|
|
|
public function testEditMinor() {
|
|
$title = Title::makeTitle( NS_HELP, 'TestEditMinor' );
|
|
|
|
$this->editPage( $title, 'Some text' );
|
|
|
|
$this->doApiRequestWithToken( [
|
|
'action' => 'edit',
|
|
'title' => $title->getPrefixedText(),
|
|
'text' => 'Different text',
|
|
'minor' => '',
|
|
] );
|
|
|
|
$revisionStore = $this->getServiceContainer()->getRevisionStore();
|
|
$revision = $revisionStore->getRevisionByTitle( $title );
|
|
$this->assertTrue( $revision->isMinor() );
|
|
}
|
|
|
|
public function testEditRecreate() {
|
|
$title = Title::makeTitle( NS_HELP, 'TestEditRecreate' );
|
|
|
|
$startTime = MWTimestamp::convert( TS_MW, time() - 1 );
|
|
|
|
$this->editPage( $title, 'Some text' );
|
|
|
|
$pageObj = $this->getServiceContainer()->getWikiPageFactory()->newFromTitle( $title );
|
|
$this->deletePage( $pageObj );
|
|
|
|
$this->assertFalse( $pageObj->exists() );
|
|
|
|
$this->doApiRequestWithToken( [
|
|
'action' => 'edit',
|
|
'title' => $title->getPrefixedText(),
|
|
'text' => 'Different text',
|
|
'starttimestamp' => $startTime,
|
|
'recreate' => '',
|
|
] );
|
|
|
|
$this->assertTrue( $title->exists( IDBAccessObject::READ_LATEST ) );
|
|
}
|
|
|
|
public function testEditWatch() {
|
|
$title = Title::makeTitle( NS_HELP, 'TestEditWatch' );
|
|
$user = $this->getTestSysop()->getUser();
|
|
$watchlistManager = $this->getServiceContainer()->getWatchlistManager();
|
|
|
|
$this->doApiRequestWithToken( [
|
|
'action' => 'edit',
|
|
'title' => $title->getPrefixedText(),
|
|
'text' => 'Some text',
|
|
'watch' => '',
|
|
'watchlistexpiry' => '99990123000000',
|
|
] );
|
|
|
|
$this->assertTrue( $title->exists( IDBAccessObject::READ_LATEST ) );
|
|
$this->assertTrue( $watchlistManager->isWatched( $user, $title ) );
|
|
$this->assertTrue( $watchlistManager->isTempWatched( $user, $title ) );
|
|
}
|
|
|
|
public function testEditUnwatch() {
|
|
$title = Title::makeTitle( NS_HELP, 'TestEditUnwatch' );
|
|
$user = $this->getTestSysop()->getUser();
|
|
|
|
$watchlistManager = $this->getServiceContainer()->getWatchlistManager();
|
|
$watchlistManager->addWatch( $user, $title );
|
|
|
|
$this->assertFalse( $title->exists() );
|
|
$this->assertTrue( $watchlistManager->isWatched( $user, $title ) );
|
|
|
|
$this->doApiRequestWithToken( [
|
|
'action' => 'edit',
|
|
'title' => $title->getPrefixedText(),
|
|
'text' => 'Some text',
|
|
'unwatch' => '',
|
|
] );
|
|
|
|
$this->assertTrue( $title->exists( IDBAccessObject::READ_LATEST ) );
|
|
$this->assertFalse( $watchlistManager->isWatched( $user, $title ) );
|
|
}
|
|
|
|
public function testEditWithTag() {
|
|
$name = 'Help:' . ucfirst( __FUNCTION__ );
|
|
|
|
$this->getServiceContainer()->getChangeTagsStore()->defineTag( 'custom tag' );
|
|
|
|
$revId = $this->doApiRequestWithToken( [
|
|
'action' => 'edit',
|
|
'title' => $name,
|
|
'text' => 'Some text',
|
|
'tags' => 'custom tag',
|
|
] )[0]['edit']['newrevid'];
|
|
|
|
$this->assertSame( 'custom tag', $this->getDb()->newSelectQueryBuilder()
|
|
->select( 'ctd_name' )
|
|
->from( 'change_tag' )
|
|
->join( 'change_tag_def', null, 'ctd_id = ct_tag_id' )
|
|
->where( [ 'ct_rev_id' => $revId ] )
|
|
->caller( __METHOD__ )->fetchField() );
|
|
}
|
|
|
|
public function testEditWithoutTagPermission() {
|
|
$title = Title::makeTitle( NS_HELP, 'TestEditWithoutTagPermission' );
|
|
|
|
$this->expectApiErrorCode( 'tags-apply-no-permission' );
|
|
|
|
$this->assertFalse( $title->exists( IDBAccessObject::READ_LATEST ) );
|
|
|
|
$this->getServiceContainer()->getChangeTagsStore()->defineTag( 'custom tag' );
|
|
$this->overrideConfigValue(
|
|
MainConfigNames::RevokePermissions,
|
|
[ 'user' => [ 'applychangetags' => true ] ]
|
|
);
|
|
|
|
try {
|
|
$this->doApiRequestWithToken( [
|
|
'action' => 'edit',
|
|
'title' => $title->getPrefixedText(),
|
|
'text' => 'Some text',
|
|
'tags' => 'custom tag',
|
|
] );
|
|
} finally {
|
|
$this->assertFalse( $title->exists( IDBAccessObject::READ_LATEST ) );
|
|
}
|
|
}
|
|
|
|
public function testEditAbortedByEditPageHookWithResult() {
|
|
$title = Title::makeTitle( NS_HELP, 'TestEditAbortedByEditPageHookWithResult' );
|
|
|
|
$this->setTemporaryHook( 'EditFilterMergedContent',
|
|
static function ( $unused1, $unused2, Status $status ) {
|
|
$status->statusData = [ 'msg' => 'A message for you!' ];
|
|
return false;
|
|
} );
|
|
|
|
$res = $this->doApiRequestWithToken( [
|
|
'action' => 'edit',
|
|
'title' => $title->getPrefixedText(),
|
|
'text' => 'Some text',
|
|
] );
|
|
|
|
$this->assertFalse( $title->exists( IDBAccessObject::READ_LATEST ) );
|
|
$this->assertSame( [ 'edit' => [ 'msg' => 'A message for you!',
|
|
'result' => 'Failure' ] ], $res[0] );
|
|
}
|
|
|
|
public function testEditAbortedByEditPageHookWithNoResult() {
|
|
$title = Title::makeTitle( NS_HELP, 'TestEditAbortedByEditPageHookWithNoResult' );
|
|
|
|
$this->expectApiErrorCode( 'hookaborted' );
|
|
|
|
$this->setTemporaryHook( 'EditFilterMergedContent',
|
|
static function () {
|
|
return false;
|
|
}
|
|
);
|
|
|
|
try {
|
|
$this->doApiRequestWithToken( [
|
|
'action' => 'edit',
|
|
'title' => $title->getPrefixedText(),
|
|
'text' => 'Some text',
|
|
] );
|
|
} finally {
|
|
$this->assertFalse( $title->exists( IDBAccessObject::READ_LATEST ) );
|
|
}
|
|
}
|
|
|
|
public function testEditWhileBlocked() {
|
|
$name = 'Help:' . ucfirst( __FUNCTION__ );
|
|
|
|
$blockStore = $this->getServiceContainer()->getDatabaseBlockStore();
|
|
$this->assertNull( $blockStore->newFromTarget( '127.0.0.1' ) );
|
|
|
|
$user = $this->getTestSysop()->getUser();
|
|
$block = new DatabaseBlock( [
|
|
'address' => $user->getName(),
|
|
'by' => $user,
|
|
'reason' => 'Capriciousness',
|
|
'timestamp' => '19370101000000',
|
|
'expiry' => 'infinity',
|
|
'enableAutoblock' => true,
|
|
] );
|
|
$blockStore->insertBlock( $block );
|
|
|
|
try {
|
|
$this->doApiRequestWithToken( [
|
|
'action' => 'edit',
|
|
'title' => $name,
|
|
'text' => 'Some text',
|
|
] );
|
|
$this->fail( 'Expected exception not thrown' );
|
|
} catch ( ApiUsageException $ex ) {
|
|
$this->assertApiErrorCode( 'blocked', $ex );
|
|
$this->assertNotNull( $blockStore->newFromTarget( '127.0.0.1' ), 'Autoblock spread' );
|
|
}
|
|
}
|
|
|
|
public function testEditWhileReadOnly() {
|
|
$name = 'Help:' . ucfirst( __FUNCTION__ );
|
|
|
|
// Create the test user before making the DB readonly
|
|
$user = $this->getTestSysop()->getUser();
|
|
$this->expectApiErrorCode( 'readonly' );
|
|
|
|
$svc = $this->getServiceContainer()->getReadOnlyMode();
|
|
$svc->setReason( "Read-only for testing" );
|
|
|
|
try {
|
|
$this->doApiRequestWithToken( [
|
|
'action' => 'edit',
|
|
'title' => $name,
|
|
'text' => 'Some text',
|
|
], null, $user );
|
|
} finally {
|
|
$svc->setReason( false );
|
|
}
|
|
}
|
|
|
|
public function testCreateImageRedirectAnon() {
|
|
$this->disableAutoCreateTempUser();
|
|
$name = 'File:' . ucfirst( __FUNCTION__ );
|
|
|
|
$this->expectApiErrorCode( 'noimageredirect-anon' );
|
|
|
|
$this->doApiRequestWithToken( [
|
|
'action' => 'edit',
|
|
'title' => $name,
|
|
'text' => '#REDIRECT [[File:Other file.png]]',
|
|
], null, new User() );
|
|
}
|
|
|
|
public function testCreateImageRedirectLoggedIn() {
|
|
$name = 'File:' . ucfirst( __FUNCTION__ );
|
|
|
|
$this->expectApiErrorCode( 'noimageredirect' );
|
|
|
|
$this->overrideConfigValue(
|
|
MainConfigNames::RevokePermissions,
|
|
[ 'user' => [ 'upload' => true ] ]
|
|
);
|
|
|
|
$this->doApiRequestWithToken( [
|
|
'action' => 'edit',
|
|
'title' => $name,
|
|
'text' => '#REDIRECT [[File:Other file.png]]',
|
|
] );
|
|
}
|
|
|
|
public function testTooBigEdit() {
|
|
$name = 'Help:' . ucfirst( __FUNCTION__ );
|
|
|
|
$this->expectApiErrorCode( 'contenttoobig' );
|
|
|
|
$this->overrideConfigValue( MainConfigNames::MaxArticleSize, 1 );
|
|
|
|
$text = str_repeat( '!', 1025 );
|
|
|
|
$this->doApiRequestWithToken( [
|
|
'action' => 'edit',
|
|
'title' => $name,
|
|
'text' => $text,
|
|
] );
|
|
}
|
|
|
|
public function testProhibitedAnonymousEdit() {
|
|
$name = 'Help:' . ucfirst( __FUNCTION__ );
|
|
|
|
$this->expectApiErrorCode( 'permissiondenied' );
|
|
|
|
$this->overrideConfigValue(
|
|
MainConfigNames::RevokePermissions,
|
|
[ '*' => [ 'edit' => true ] ]
|
|
);
|
|
|
|
$this->doApiRequestWithToken( [
|
|
'action' => 'edit',
|
|
'title' => $name,
|
|
'text' => 'Some text',
|
|
], null, new User() );
|
|
}
|
|
|
|
public function testProhibitedChangeContentModel() {
|
|
$name = 'Help:' . ucfirst( __FUNCTION__ );
|
|
|
|
$this->expectApiErrorCode( 'cantchangecontentmodel' );
|
|
|
|
$this->overrideConfigValue(
|
|
MainConfigNames::RevokePermissions,
|
|
[ 'user' => [ 'editcontentmodel' => true ] ]
|
|
);
|
|
|
|
$this->doApiRequestWithToken( [
|
|
'action' => 'edit',
|
|
'title' => $name,
|
|
'text' => 'Some text',
|
|
'contentmodel' => 'json',
|
|
] );
|
|
}
|
|
|
|
public function testMidEditContentModelMismatch() {
|
|
$title = Title::makeTitle( NS_HELP, 'TestMidEditContentModelMismatch' );
|
|
|
|
$page = $this->getServiceContainer()->getWikiPageFactory()->newFromTitle( $title );
|
|
|
|
// base edit, currently in Wikitext
|
|
$page->doUserEditContent(
|
|
new WikitextContent( "Foo" ),
|
|
$this->getTestSysop()->getUser(),
|
|
"testing 1",
|
|
EDIT_NEW
|
|
);
|
|
$this->forceRevisionDate( $page, '20120101000000' );
|
|
$baseId = $page->getRevisionRecord()->getId();
|
|
|
|
// Attempt edit in Javascript. This may happen, for instance, if we
|
|
// started editing the base content while it was in Javascript and
|
|
// before we save it was changed to Wikitext (base edit model).
|
|
$page->doUserEditContent(
|
|
new JavaScriptContent( "Bar" ),
|
|
$this->getTestUser()->getUser(),
|
|
"testing 2",
|
|
EDIT_UPDATE
|
|
);
|
|
$this->forceRevisionDate( $page, '20120101020202' );
|
|
|
|
// ContentHandler may throw exception if we attempt saving the above, so we will
|
|
// handle that with contentmodel-mismatch error. Test this is the case.
|
|
try {
|
|
$this->doApiRequestWithToken( [
|
|
'action' => 'edit',
|
|
'title' => $title->getPrefixedText(),
|
|
'text' => 'different content models!',
|
|
'baserevid' => $baseId,
|
|
] );
|
|
$this->fail( "Should have raised an ApiUsageException" );
|
|
} catch ( ApiUsageException $e ) {
|
|
$this->assertApiErrorCode( 'contentmodel-mismatch', $e );
|
|
}
|
|
}
|
|
}
|