2020-01-11 23:49:41 +00:00
|
|
|
<?php
|
|
|
|
|
|
2021-03-02 21:29:46 +00:00
|
|
|
use MediaWiki\Page\PageIdentity;
|
|
|
|
|
use MediaWiki\Permissions\Authority;
|
|
|
|
|
use MediaWiki\Permissions\PermissionStatus;
|
|
|
|
|
use MediaWiki\Tests\Unit\MockServiceDependenciesTrait;
|
|
|
|
|
use MediaWiki\Tests\Unit\Permissions\MockAuthorityTrait;
|
2023-03-01 20:33:26 +00:00
|
|
|
use MediaWiki\Title\Title;
|
2020-01-11 23:49:41 +00:00
|
|
|
|
|
|
|
|
/**
|
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
|
2021-03-02 21:29:46 +00:00
|
|
|
* @method ContentModelChange newServiceInstance(string $serviceClass, array $parameterOverrides)
|
2020-01-11 23:49:41 +00:00
|
|
|
*/
|
2020-06-30 15:09:24 +00:00
|
|
|
class ContentModelChangeTest extends MediaWikiIntegrationTestCase {
|
2021-03-02 21:29:46 +00:00
|
|
|
use MockAuthorityTrait;
|
|
|
|
|
use MockServiceDependenciesTrait;
|
2020-01-11 23:49:41 +00:00
|
|
|
|
2021-07-22 03:11:47 +00:00
|
|
|
protected function setUp(): void {
|
2020-01-11 23:49:41 +00:00
|
|
|
parent::setUp();
|
|
|
|
|
|
|
|
|
|
$this->tablesUsed = array_merge(
|
|
|
|
|
$this->tablesUsed,
|
|
|
|
|
[ 'change_tag', 'change_tag_def', 'logging' ]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$this->getExistingTestPage( 'ExistingPage' );
|
|
|
|
|
$this->mergeMwGlobalArrayValue( 'wgContentHandlers', [
|
|
|
|
|
'testing' => 'DummyContentHandlerForTesting',
|
|
|
|
|
] );
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-30 19:10:58 +00:00
|
|
|
private function newContentModelChange(
|
2021-03-02 21:29:46 +00:00
|
|
|
Authority $performer,
|
2020-05-30 19:10:58 +00:00
|
|
|
WikiPage $page,
|
|
|
|
|
string $newModel
|
|
|
|
|
) {
|
2021-03-02 21:29:46 +00:00
|
|
|
return $this->getServiceContainer()
|
2020-05-30 19:10:58 +00:00
|
|
|
->getContentModelChangeFactory()
|
2021-03-02 21:29:46 +00:00
|
|
|
->newContentModelChange( $performer, $page, $newModel );
|
2020-05-30 19:10:58 +00:00
|
|
|
}
|
|
|
|
|
|
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(),
|
2021-11-21 19:13:24 +00:00
|
|
|
'`ExistingPage` should be wikitext'
|
2020-01-11 23:49:41 +00:00
|
|
|
);
|
|
|
|
|
|
2020-05-30 19:10:58 +00:00
|
|
|
$change = $this->newContentModelChange(
|
2021-03-02 21:29:46 +00:00
|
|
|
$this->mockRegisteredAuthorityWithPermissions( [ 'editcontentmodel' ] ),
|
2020-01-11 23:49:41 +00:00
|
|
|
$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' );
|
2021-06-24 08:42:19 +00:00
|
|
|
$wikipage->doUserEditContent(
|
2020-01-11 23:49:41 +00:00
|
|
|
ContentHandler::makeContent( $invalidJSON, $wikipage->getTitle() ),
|
2021-06-24 08:42:19 +00:00
|
|
|
$this->getTestSysop()->getUser(),
|
2020-01-11 23:49:41 +00:00
|
|
|
'EditSummaryForThisTest',
|
2021-06-24 08:42:19 +00:00
|
|
|
EDIT_UPDATE | EDIT_SUPPRESS_RC
|
2020-01-11 23:49:41 +00:00
|
|
|
);
|
|
|
|
|
$this->assertSame(
|
|
|
|
|
'wikitext',
|
|
|
|
|
$wikipage->getTitle()->getContentModel(),
|
2021-11-21 19:13:24 +00:00
|
|
|
'`PageWithTextThatIsNotValidJSON` should be wikitext at first'
|
2020-01-11 23:49:41 +00:00
|
|
|
);
|
|
|
|
|
|
2020-05-30 19:10:58 +00:00
|
|
|
$change = $this->newContentModelChange(
|
2021-03-02 21:29:46 +00:00
|
|
|
$this->mockRegisteredAuthorityWithPermissions( [ 'editcontentmodel' ] ),
|
2020-01-11 23:49:41 +00:00
|
|
|
$wikipage,
|
|
|
|
|
'json'
|
|
|
|
|
);
|
|
|
|
|
$status = $change->doContentModelChange(
|
|
|
|
|
RequestContext::getMain(),
|
|
|
|
|
__METHOD__ . ' comment',
|
|
|
|
|
false
|
|
|
|
|
);
|
2022-08-14 15:49:20 +00:00
|
|
|
$this->assertStatusError( 'invalid-json-data', $status );
|
2020-01-11 23:49:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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 ),
|
2021-11-21 19:13:24 +00:00
|
|
|
'`ExistingPage` should be wikitext'
|
2020-01-11 23:49:41 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$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(
|
2021-03-02 21:29:46 +00:00
|
|
|
$this->mockRegisteredAuthorityWithPermissions( [ 'editcontentmodel' ] ),
|
2020-01-11 23:49:41 +00:00
|
|
|
$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' );
|
2021-06-24 08:42:19 +00:00
|
|
|
$wikipage->doUserEditContent(
|
2020-01-11 23:49:41 +00:00
|
|
|
ContentHandler::makeContent( 'Text', $wikipage->getTitle() ),
|
2021-06-24 08:42:19 +00:00
|
|
|
$this->getTestSysop()->getUser(),
|
2020-01-11 23:49:41 +00:00
|
|
|
'Ensure a revision exists',
|
2021-06-24 08:42:19 +00:00
|
|
|
EDIT_UPDATE | EDIT_SUPPRESS_RC
|
2020-01-11 23:49:41 +00:00
|
|
|
);
|
|
|
|
|
$this->assertSame(
|
|
|
|
|
'wikitext',
|
|
|
|
|
$wikipage->getTitle()->getContentModel( Title::READ_LATEST ),
|
2021-11-21 19:13:24 +00:00
|
|
|
'`ExistingPage` should be wikitext'
|
2020-01-11 23:49:41 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$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(
|
2021-03-02 21:29:46 +00:00
|
|
|
$this->mockRegisteredAuthorityWithPermissions( [ 'editcontentmodel' ] ),
|
2020-01-11 23:49:41 +00:00
|
|
|
$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' );
|
2022-09-01 21:18:41 +00:00
|
|
|
$wikipage = $this->getServiceContainer()->getWikiPageFactory()->newFromTitle( $title );
|
2020-01-11 23:49:41 +00:00
|
|
|
|
|
|
|
|
$dummyContent = ContentHandler::getForModelID( 'testing' )->makeEmptyContent();
|
2021-06-24 08:42:19 +00:00
|
|
|
$wikipage->doUserEditContent(
|
2020-01-11 23:49:41 +00:00
|
|
|
$dummyContent,
|
2021-06-24 08:42:19 +00:00
|
|
|
$this->getTestSysop()->getUser(),
|
2020-01-11 23:49:41 +00:00
|
|
|
'EditSummaryForThisTest',
|
2021-06-24 08:42:19 +00:00
|
|
|
EDIT_NEW | EDIT_SUPPRESS_RC
|
2020-01-11 23:49:41 +00:00
|
|
|
);
|
|
|
|
|
$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(
|
2021-03-02 21:29:46 +00:00
|
|
|
$this->mockRegisteredAuthorityWithPermissions( [ 'editcontentmodel' ] ),
|
2020-01-11 23:49:41 +00:00
|
|
|
$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(
|
2021-03-02 21:29:46 +00:00
|
|
|
$this->mockRegisteredAuthorityWithoutPermissions( [ 'applychangetags' ] ),
|
2020-01-11 23:49:41 +00:00
|
|
|
$this->getExistingTestPage( 'ExistingPage' ),
|
|
|
|
|
'text'
|
|
|
|
|
);
|
|
|
|
|
$status = $change->setTags( [ 'edit content model tag' ] );
|
|
|
|
|
$this->assertEquals(
|
|
|
|
|
Status::newFatal( 'tags-apply-no-permission' ),
|
|
|
|
|
$status
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2021-03-02 21:29:46 +00:00
|
|
|
* @covers ContentModelChange::authorizeChange
|
|
|
|
|
* @covers ContentModelChange::probablyCanChange
|
2020-01-11 23:49:41 +00:00
|
|
|
*/
|
|
|
|
|
public function testCheckPermissions() {
|
|
|
|
|
$wikipage = $this->getExistingTestPage( 'ExistingPage' );
|
|
|
|
|
$title = $wikipage->getTitle();
|
|
|
|
|
$currentContentModel = $title->getContentModel( Title::READ_LATEST );
|
2021-03-02 21:29:46 +00:00
|
|
|
$newContentModel = 'text';
|
2020-01-11 23:49:41 +00:00
|
|
|
|
|
|
|
|
$this->assertSame(
|
|
|
|
|
'wikitext',
|
|
|
|
|
$currentContentModel,
|
2021-11-21 19:13:24 +00:00
|
|
|
'`ExistingPage` should be wikitext'
|
2020-01-11 23:49:41 +00:00
|
|
|
);
|
|
|
|
|
|
2021-04-29 16:24:12 +00:00
|
|
|
$performer = $this->mockRegisteredAuthority( static function (
|
2021-03-02 21:29:46 +00:00
|
|
|
string $permission,
|
|
|
|
|
PageIdentity $page,
|
|
|
|
|
PermissionStatus $status
|
|
|
|
|
) use ( $currentContentModel, $newContentModel ) {
|
|
|
|
|
$title = Title::castFromPageIdentity( $page );
|
|
|
|
|
if ( $permission === 'editcontentmodel' && $title->hasContentModel( $currentContentModel ) ) {
|
|
|
|
|
$status->fatal( 'no edit old content model' );
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if ( $permission === 'editcontentmodel' && $title->hasContentModel( $newContentModel ) ) {
|
|
|
|
|
$status->fatal( 'no edit new content model' );
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if ( $permission === 'edit' && $title->hasContentModel( $currentContentModel ) ) {
|
|
|
|
|
$status->fatal( 'no edit at all old content model' );
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if ( $permission === 'edit' && $title->hasContentModel( $newContentModel ) ) {
|
|
|
|
|
$status->fatal( 'no edit at all new content model' );
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
} );
|
2020-01-11 23:49:41 +00:00
|
|
|
|
2021-03-02 21:29:46 +00:00
|
|
|
$change = $this->newServiceInstance(
|
|
|
|
|
ContentModelChange::class,
|
2020-01-11 23:49:41 +00:00
|
|
|
[
|
2021-03-02 21:29:46 +00:00
|
|
|
'performer' => $performer,
|
|
|
|
|
'page' => $wikipage,
|
|
|
|
|
'newModel' => $newContentModel
|
|
|
|
|
]
|
2020-01-11 23:49:41 +00:00
|
|
|
);
|
2021-03-02 21:29:46 +00:00
|
|
|
|
|
|
|
|
foreach ( [ 'probablyCanChange', 'authorizeChange' ] as $method ) {
|
|
|
|
|
$status = $change->$method();
|
|
|
|
|
$this->assertArrayEquals(
|
|
|
|
|
[
|
|
|
|
|
[ 'no edit new content model' ],
|
|
|
|
|
[ 'no edit old content model' ],
|
|
|
|
|
[ 'no edit at all old content model' ],
|
|
|
|
|
[ 'no edit at all new content model' ],
|
|
|
|
|
],
|
|
|
|
|
$status->toLegacyErrorArray()
|
|
|
|
|
);
|
|
|
|
|
}
|
2020-01-11 23:49:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2021-03-02 21:29:46 +00:00
|
|
|
* @covers ContentModelChange::doContentModelChange
|
2020-01-11 23:49:41 +00:00
|
|
|
*/
|
|
|
|
|
public function testCheckPermissionsThrottle() {
|
|
|
|
|
$mock = $this->getMockBuilder( User::class )
|
2021-03-20 15:18:58 +00:00
|
|
|
->onlyMethods( [ 'pingLimiter' ] )
|
2020-01-11 23:49:41 +00:00
|
|
|
->getMock();
|
|
|
|
|
$mock->expects( $this->once() )
|
|
|
|
|
->method( 'pingLimiter' )
|
2021-04-22 07:56:11 +00:00
|
|
|
->with( 'editcontentmodel' )
|
2020-01-11 23:49:41 +00:00
|
|
|
->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 );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|