2020-01-11 23:49:41 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
use MediaWiki\MediaWikiServices;
|
|
|
|
|
use MediaWiki\Permissions\PermissionManager;
|
|
|
|
|
|
|
|
|
|
/**
|
2020-05-30 19:10:58 +00:00
|
|
|
* TODO convert to a pure unit test
|
|
|
|
|
*
|
2020-01-11 23:49:41 +00:00
|
|
|
* @group Database
|
2020-05-30 19:10:58 +00:00
|
|
|
*
|
2020-04-11 08:10:18 +00:00
|
|
|
* @author DannyS712
|
2020-01-11 23:49:41 +00:00
|
|
|
*/
|
2020-06-30 15:09:24 +00:00
|
|
|
class ContentModelChangeTest extends MediaWikiIntegrationTestCase {
|
2020-01-11 23:49:41 +00:00
|
|
|
|
|
|
|
|
protected function setUp() : void {
|
|
|
|
|
parent::setUp();
|
|
|
|
|
|
|
|
|
|
$this->tablesUsed = array_merge(
|
|
|
|
|
$this->tablesUsed,
|
|
|
|
|
[ 'change_tag', 'change_tag_def', 'logging' ]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$this->getExistingTestPage( 'ExistingPage' );
|
|
|
|
|
|
|
|
|
|
$this->mergeMwGlobalArrayValue( 'wgGroupPermissions', [
|
|
|
|
|
'editcontentmodel' => [ 'editcontentmodel' => true ]
|
|
|
|
|
] );
|
|
|
|
|
|
|
|
|
|
$this->setMwGlobals( [
|
|
|
|
|
'wgRevokePermissions' => [
|
|
|
|
|
'noeditcontentmodel' => [ 'editcontentmodel' => true ],
|
|
|
|
|
'noapplychangetags' => [ 'applychangetags' => true ],
|
|
|
|
|
],
|
|
|
|
|
'wgExtraNamespaces' => [
|
|
|
|
|
12312 => 'Dummy',
|
|
|
|
|
12313 => 'Dummy_talk',
|
|
|
|
|
],
|
|
|
|
|
'wgNamespaceContentModels' => [
|
|
|
|
|
12312 => 'testing',
|
|
|
|
|
],
|
|
|
|
|
] );
|
|
|
|
|
$this->mergeMwGlobalArrayValue( 'wgContentHandlers', [
|
|
|
|
|
'testing' => 'DummyContentHandlerForTesting',
|
|
|
|
|
] );
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-30 19:10:58 +00:00
|
|
|
private function newContentModelChange(
|
|
|
|
|
User $user,
|
|
|
|
|
WikiPage $page,
|
|
|
|
|
string $newModel
|
|
|
|
|
) {
|
|
|
|
|
return MediaWikiServices::getInstance()
|
|
|
|
|
->getContentModelChangeFactory()
|
|
|
|
|
->newContentModelChange( $user, $page, $newModel );
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-11 23:49:41 +00:00
|
|
|
/**
|
|
|
|
|
* Test that the content model needs to change
|
|
|
|
|
*
|
|
|
|
|
* @covers ContentModelChange::doContentModelChange
|
|
|
|
|
*/
|
|
|
|
|
public function testChangeNeeded() {
|
|
|
|
|
$wikipage = $this->getExistingTestPage( 'ExistingPage' );
|
|
|
|
|
$this->assertSame(
|
|
|
|
|
'wikitext',
|
|
|
|
|
$wikipage->getTitle()->getContentModel(),
|
|
|
|
|
'Sanity check: `ExistingPage` should be wikitext'
|
|
|
|
|
);
|
|
|
|
|
|
2020-05-30 19:10:58 +00:00
|
|
|
$change = $this->newContentModelChange(
|
2020-01-11 23:49:41 +00:00
|
|
|
$this->getTestUser( [ 'editcontentmodel' ] )->getUser(),
|
|
|
|
|
$wikipage,
|
|
|
|
|
'wikitext'
|
|
|
|
|
);
|
|
|
|
|
$status = $change->doContentModelChange(
|
|
|
|
|
RequestContext::getMain(),
|
|
|
|
|
__METHOD__ . ' comment',
|
|
|
|
|
false
|
|
|
|
|
);
|
|
|
|
|
$this->assertEquals(
|
|
|
|
|
Status::newFatal( 'apierror-nochanges' ),
|
|
|
|
|
$status
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Test that the content needs to be valid for the requested model
|
|
|
|
|
*
|
|
|
|
|
* @covers ContentModelChange::doContentModelChange
|
|
|
|
|
*/
|
|
|
|
|
public function testInvalidContent() {
|
|
|
|
|
$invalidJSON = 'Foo\nBar\nEaster egg\nT22281';
|
|
|
|
|
$wikipage = $this->getExistingTestPage( 'PageWithTextThatIsNotValidJSON' );
|
|
|
|
|
$wikipage->doEditContent(
|
|
|
|
|
ContentHandler::makeContent( $invalidJSON, $wikipage->getTitle() ),
|
|
|
|
|
'EditSummaryForThisTest',
|
|
|
|
|
EDIT_UPDATE | EDIT_SUPPRESS_RC,
|
|
|
|
|
false,
|
|
|
|
|
$this->getTestSysop()->getUser()
|
|
|
|
|
);
|
|
|
|
|
$this->assertSame(
|
|
|
|
|
'wikitext',
|
|
|
|
|
$wikipage->getTitle()->getContentModel(),
|
|
|
|
|
'Sanity check: `PageWithTextThatIsNotValidJSON` should be wikitext at first'
|
|
|
|
|
);
|
|
|
|
|
|
2020-05-30 19:10:58 +00:00
|
|
|
$change = $this->newContentModelChange(
|
2020-01-11 23:49:41 +00:00
|
|
|
$this->getTestUser( [ 'editcontentmodel' ] )->getUser(),
|
|
|
|
|
$wikipage,
|
|
|
|
|
'json'
|
|
|
|
|
);
|
|
|
|
|
$status = $change->doContentModelChange(
|
|
|
|
|
RequestContext::getMain(),
|
|
|
|
|
__METHOD__ . ' comment',
|
|
|
|
|
false
|
|
|
|
|
);
|
|
|
|
|
$this->assertSame(
|
|
|
|
|
'invalid-content-data',
|
|
|
|
|
$status->getErrors()[0]['message']
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Test the EditFilterMergedContent hook can be intercepted
|
|
|
|
|
*
|
|
|
|
|
* @covers ContentModelChange::doContentModelChange
|
|
|
|
|
*
|
|
|
|
|
* @dataProvider provideTestEditFilterMergedContent
|
|
|
|
|
* @param string|bool $customMessage Hook message, or false
|
|
|
|
|
* @param string $expectedMessage expected fatal
|
|
|
|
|
*/
|
|
|
|
|
public function testEditFilterMergedContent( $customMessage, $expectedMessage ) {
|
|
|
|
|
$wikipage = $this->getExistingTestPage( 'ExistingPage' );
|
|
|
|
|
$this->assertSame(
|
|
|
|
|
'wikitext',
|
|
|
|
|
$wikipage->getTitle()->getContentModel( Title::READ_LATEST ),
|
|
|
|
|
'Sanity check: `ExistingPage` should be wikitext'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$this->setTemporaryHook( 'EditFilterMergedContent',
|
2021-02-07 13:10:36 +00:00
|
|
|
static function ( $unused1, $unused2, Status $status ) use ( $customMessage ) {
|
2020-01-11 23:49:41 +00:00
|
|
|
if ( $customMessage !== false ) {
|
|
|
|
|
$status->fatal( $customMessage );
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
2020-05-30 19:10:58 +00:00
|
|
|
$change = $this->newContentModelChange(
|
2020-01-11 23:49:41 +00:00
|
|
|
$this->getTestUser( [ 'editcontentmodel' ] )->getUser(),
|
|
|
|
|
$wikipage,
|
|
|
|
|
'text'
|
|
|
|
|
);
|
|
|
|
|
$status = $change->doContentModelChange(
|
|
|
|
|
RequestContext::getMain(),
|
|
|
|
|
__METHOD__ . ' comment',
|
|
|
|
|
false
|
|
|
|
|
);
|
|
|
|
|
$this->assertEquals(
|
|
|
|
|
Status::newFatal( $expectedMessage ),
|
|
|
|
|
$status
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function provideTestEditFilterMergedContent() {
|
|
|
|
|
return [
|
|
|
|
|
[ 'DannyS712 objects to this change!', 'DannyS712 objects to this change!' ],
|
|
|
|
|
[ false, 'hookaborted' ]
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Test the ContentModelCanBeUsedOn hook can be intercepted
|
|
|
|
|
*
|
|
|
|
|
* @covers ContentModelChange::doContentModelChange
|
|
|
|
|
*/
|
|
|
|
|
public function testContentModelCanBeUsedOn() {
|
|
|
|
|
$wikipage = $this->getExistingTestPage( 'ExistingPage' );
|
|
|
|
|
$wikipage->doEditContent(
|
|
|
|
|
ContentHandler::makeContent( 'Text', $wikipage->getTitle() ),
|
|
|
|
|
'Ensure a revision exists',
|
|
|
|
|
EDIT_UPDATE | EDIT_SUPPRESS_RC,
|
|
|
|
|
false,
|
|
|
|
|
$this->getTestSysop()->getUser()
|
|
|
|
|
);
|
|
|
|
|
$this->assertSame(
|
|
|
|
|
'wikitext',
|
|
|
|
|
$wikipage->getTitle()->getContentModel( Title::READ_LATEST ),
|
|
|
|
|
'Sanity check: `ExistingPage` should be wikitext'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$this->setTemporaryHook( 'ContentModelCanBeUsedOn',
|
2021-02-07 13:10:36 +00:00
|
|
|
static function ( $unused1, $unused2, &$ok ) {
|
2020-01-11 23:49:41 +00:00
|
|
|
$ok = false;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
2020-05-30 19:10:58 +00:00
|
|
|
$change = $this->newContentModelChange(
|
2020-01-11 23:49:41 +00:00
|
|
|
$this->getTestUser( [ 'editcontentmodel' ] )->getUser(),
|
|
|
|
|
$wikipage,
|
|
|
|
|
'text'
|
|
|
|
|
);
|
|
|
|
|
$status = $change->doContentModelChange(
|
|
|
|
|
RequestContext::getMain(),
|
|
|
|
|
__METHOD__ . ' comment',
|
|
|
|
|
false
|
|
|
|
|
);
|
|
|
|
|
$this->assertEquals(
|
|
|
|
|
Status::newFatal(
|
|
|
|
|
'apierror-changecontentmodel-cannotbeused',
|
|
|
|
|
'plain text',
|
|
|
|
|
Message::plaintextParam( $wikipage->getTitle()->getPrefixedText() )
|
|
|
|
|
),
|
|
|
|
|
$status
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Test that content handler must support direct editing
|
|
|
|
|
*
|
|
|
|
|
* @covers ContentModelChange::doContentModelChange
|
|
|
|
|
*/
|
|
|
|
|
public function testNoDirectEditing() {
|
|
|
|
|
$title = Title::newFromText( 'Dummy:NoDirectEditing' );
|
|
|
|
|
$wikipage = WikiPage::factory( $title );
|
|
|
|
|
|
|
|
|
|
$dummyContent = ContentHandler::getForModelID( 'testing' )->makeEmptyContent();
|
|
|
|
|
$wikipage->doEditContent(
|
|
|
|
|
$dummyContent,
|
|
|
|
|
'EditSummaryForThisTest',
|
|
|
|
|
EDIT_NEW | EDIT_SUPPRESS_RC,
|
|
|
|
|
false,
|
|
|
|
|
$this->getTestSysop()->getUser()
|
|
|
|
|
);
|
|
|
|
|
$this->assertSame(
|
|
|
|
|
'testing',
|
|
|
|
|
$title->getContentModel( Title::READ_LATEST ),
|
|
|
|
|
'Dummy:NoDirectEditing should start with the `testing` content model'
|
|
|
|
|
);
|
|
|
|
|
|
2020-05-30 19:10:58 +00:00
|
|
|
$change = $this->newContentModelChange(
|
2020-01-11 23:49:41 +00:00
|
|
|
$this->getTestUser( [ 'editcontentmodel' ] )->getUser(),
|
|
|
|
|
$wikipage,
|
|
|
|
|
'text'
|
|
|
|
|
);
|
|
|
|
|
$status = $change->doContentModelChange(
|
|
|
|
|
RequestContext::getMain(),
|
|
|
|
|
__METHOD__ . ' comment',
|
|
|
|
|
false
|
|
|
|
|
);
|
|
|
|
|
$this->assertEquals(
|
|
|
|
|
Status::newFatal(
|
|
|
|
|
'apierror-changecontentmodel-nodirectediting',
|
|
|
|
|
ContentHandler::getLocalizedName( 'testing' )
|
|
|
|
|
),
|
|
|
|
|
$status
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @covers ContentModelChange::setTags
|
|
|
|
|
*/
|
|
|
|
|
public function testCannotApplyTags() {
|
|
|
|
|
ChangeTags::defineTag( 'edit content model tag' );
|
|
|
|
|
|
2020-05-30 19:10:58 +00:00
|
|
|
$change = $this->newContentModelChange(
|
2020-01-11 23:49:41 +00:00
|
|
|
$this->getTestUser( [ 'noapplychangetags' ] )->getUser(),
|
|
|
|
|
$this->getExistingTestPage( 'ExistingPage' ),
|
|
|
|
|
'text'
|
|
|
|
|
);
|
|
|
|
|
$status = $change->setTags( [ 'edit content model tag' ] );
|
|
|
|
|
$this->assertEquals(
|
|
|
|
|
Status::newFatal( 'tags-apply-no-permission' ),
|
|
|
|
|
$status
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @covers ContentModelChange::checkPermissions
|
|
|
|
|
*/
|
|
|
|
|
public function testCheckPermissions() {
|
|
|
|
|
$user = $this->getTestUser()->getUser();
|
|
|
|
|
$wikipage = $this->getExistingTestPage( 'ExistingPage' );
|
|
|
|
|
$title = $wikipage->getTitle();
|
|
|
|
|
$currentContentModel = $title->getContentModel( Title::READ_LATEST );
|
|
|
|
|
|
|
|
|
|
$this->assertSame(
|
|
|
|
|
'wikitext',
|
|
|
|
|
$currentContentModel,
|
|
|
|
|
'Sanity check: `ExistingPage` should be wikitext'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$newContentModel = 'text';
|
|
|
|
|
$mock = $this->getMockBuilder( PermissionManager::class )
|
|
|
|
|
->disableOriginalConstructor()
|
|
|
|
|
->setMethods( [ 'getPermissionErrors' ] )
|
|
|
|
|
->getMock();
|
|
|
|
|
$mock->expects( $this->exactly( 4 ) )
|
|
|
|
|
->method( 'getPermissionErrors' )
|
|
|
|
|
->withConsecutive(
|
|
|
|
|
[
|
|
|
|
|
$this->equalTo( 'editcontentmodel' ),
|
2021-02-07 13:10:36 +00:00
|
|
|
$this->callback( static function ( User $userInMock ) use ( $user ) {
|
2020-01-11 23:49:41 +00:00
|
|
|
return $user->equals( $userInMock );
|
|
|
|
|
} ),
|
2021-02-07 13:10:36 +00:00
|
|
|
$this->callback( static function ( Title $titleInMock ) use ( $title, $currentContentModel ) {
|
2020-01-11 23:49:41 +00:00
|
|
|
return $title->equals( $titleInMock ) &&
|
|
|
|
|
$titleInMock->hasContentModel( $currentContentModel );
|
|
|
|
|
} )
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
$this->equalTo( 'edit' ),
|
2021-02-07 13:10:36 +00:00
|
|
|
$this->callback( static function ( User $userInMock ) use ( $user ) {
|
2020-01-11 23:49:41 +00:00
|
|
|
return $user->equals( $userInMock );
|
|
|
|
|
} ),
|
2021-02-07 13:10:36 +00:00
|
|
|
$this->callback( static function ( Title $titleInMock ) use ( $title, $currentContentModel ) {
|
2020-01-11 23:49:41 +00:00
|
|
|
return $title->equals( $titleInMock ) &&
|
|
|
|
|
$titleInMock->hasContentModel( $currentContentModel );
|
|
|
|
|
} )
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
$this->equalTo( 'editcontentmodel' ),
|
2021-02-07 13:10:36 +00:00
|
|
|
$this->callback( static function ( User $userInMock ) use ( $user ) {
|
2020-01-11 23:49:41 +00:00
|
|
|
return $user->equals( $userInMock );
|
|
|
|
|
} ),
|
2021-02-07 13:10:36 +00:00
|
|
|
$this->callback( static function ( Title $titleInMock ) use ( $title, $newContentModel ) {
|
2020-01-11 23:49:41 +00:00
|
|
|
return $title->equals( $titleInMock ) &&
|
|
|
|
|
$titleInMock->hasContentModel( $newContentModel );
|
|
|
|
|
} )
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
$this->equalTo( 'edit' ),
|
2021-02-07 13:10:36 +00:00
|
|
|
$this->callback( static function ( User $userInMock ) use ( $user ) {
|
2020-01-11 23:49:41 +00:00
|
|
|
return $user->equals( $userInMock );
|
|
|
|
|
} ),
|
2021-02-07 13:10:36 +00:00
|
|
|
$this->callback( static function ( Title $titleInMock ) use ( $title, $newContentModel ) {
|
2020-01-11 23:49:41 +00:00
|
|
|
return $title->equals( $titleInMock ) &&
|
|
|
|
|
$titleInMock->hasContentModel( $newContentModel );
|
|
|
|
|
} )
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
->will(
|
|
|
|
|
$this->onConsecutiveCalls(
|
|
|
|
|
[ [ 'no edit content model' ] ],
|
|
|
|
|
[ [ 'no editing at all' ] ],
|
|
|
|
|
[ [ 'no edit content model' ] ],
|
|
|
|
|
[ [ 'no editing', 'with new content model' ] ]
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
|
2020-05-30 19:10:58 +00:00
|
|
|
$services = MediaWikiServices::getInstance();
|
2020-01-11 23:49:41 +00:00
|
|
|
$change = new ContentModelChange(
|
2020-05-30 19:10:58 +00:00
|
|
|
$services->getContentHandlerFactory(),
|
|
|
|
|
$services->getHookContainer(),
|
2020-01-11 23:49:41 +00:00
|
|
|
$mock,
|
2020-05-30 19:10:58 +00:00
|
|
|
$services->getRevisionLookup(),
|
|
|
|
|
$user,
|
2020-01-11 23:49:41 +00:00
|
|
|
$wikipage,
|
|
|
|
|
$newContentModel
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$errors = $change->checkPermissions();
|
|
|
|
|
$this->assertArrayEquals(
|
|
|
|
|
[
|
|
|
|
|
[ 'no edit content model' ],
|
|
|
|
|
[ 'no editing at all' ],
|
|
|
|
|
[ 'no editing', 'with new content model' ]
|
|
|
|
|
],
|
|
|
|
|
$errors
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @covers ContentModelChange::checkPermissions
|
|
|
|
|
*/
|
|
|
|
|
public function testCheckPermissionsThrottle() {
|
|
|
|
|
$mock = $this->getMockBuilder( User::class )
|
|
|
|
|
->setMethods( [ 'pingLimiter' ] )
|
|
|
|
|
->getMock();
|
|
|
|
|
$mock->expects( $this->once() )
|
|
|
|
|
->method( 'pingLimiter' )
|
|
|
|
|
->with( $this->equalTo( 'editcontentmodel' ) )
|
|
|
|
|
->willReturn( true );
|
|
|
|
|
|
2020-05-30 19:10:58 +00:00
|
|
|
$change = $this->newContentModelChange(
|
2020-01-11 23:49:41 +00:00
|
|
|
$mock,
|
|
|
|
|
$this->getNonexistingTestPage( 'NonExistingPage' ),
|
|
|
|
|
'text'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$context = new RequestContext();
|
|
|
|
|
$comment = 'comment';
|
|
|
|
|
$bot = true;
|
|
|
|
|
|
|
|
|
|
$this->expectException( ThrottledError::class );
|
|
|
|
|
|
|
|
|
|
$change->doContentModelChange( $context, $comment, $bot );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|