EditPage: Add two more constraints

- AutoSummaryMissingSummaryConstraint
- SelfRedirectConstraint

Bug: T157658
Change-Id: I0bc29d81ec3df6228a04f41df66b53b113792e38
This commit is contained in:
DannyS712 2020-11-11 00:28:56 +00:00
parent 6f901ef17c
commit f10795c3cd
7 changed files with 444 additions and 48 deletions

View file

@ -22,6 +22,7 @@
use MediaWiki\Block\DatabaseBlock;
use MediaWiki\Content\IContentHandlerFactory;
use MediaWiki\EditPage\Constraint\AutoSummaryMissingSummaryConstraint;
use MediaWiki\EditPage\Constraint\ChangeTagsConstraint;
use MediaWiki\EditPage\Constraint\DefaultTextConstraint;
use MediaWiki\EditPage\Constraint\EditConstraintRunner;
@ -29,6 +30,7 @@ use MediaWiki\EditPage\Constraint\EditFilterMergedContentHookConstraint;
use MediaWiki\EditPage\Constraint\MissingCommentConstraint;
use MediaWiki\EditPage\Constraint\NewSectionMissingSummaryConstraint;
use MediaWiki\EditPage\Constraint\PageSizeConstraint;
use MediaWiki\EditPage\Constraint\SelfRedirectConstraint;
use MediaWiki\EditPage\Constraint\SpamRegexConstraint;
use MediaWiki\EditPage\Constraint\UnicodeConstraint;
use MediaWiki\EditPage\Constraint\UserBlockConstraint;
@ -2277,22 +2279,8 @@ class EditPage implements IEditObject {
$this->minoredit
)
);
// Check the constraints
if ( $constraintRunner->checkConstraints() === false ) {
$failed = $constraintRunner->getFailedConstraint();
if ( $failed instanceof EditFilterMergedContentHookConstraint ) {
$this->hookError = $failed->getHookError();
}
$statusValue = $failed->getLegacyStatus();
$status = Status::wrap( $statusValue );
return $status;
}
// END OF MIGRATION TO EDITCONSTRAINT SYSTEM (continued below)
if ( $this->section == 'new' ) {
// BEGINNING OF MIGRATION TO EDITCONSTRAINT SYSTEM (see T157658)
// Create a new runner to avoid rechecking the prior constraints, use the same factory
$constraintRunner = new EditConstraintRunner();
$constraintRunner->addConstraint(
new NewSectionMissingSummaryConstraint(
$this->summary,
@ -2302,29 +2290,35 @@ class EditPage implements IEditObject {
$constraintRunner->addConstraint(
new MissingCommentConstraint( $this->textbox1 )
);
// Check the constraints
if ( $constraintRunner->checkConstraints() === false ) {
$failed = $constraintRunner->getFailedConstraint();
if ( $failed instanceof NewSectionMissingSummaryConstraint ) {
$this->missingSummary = true;
} elseif ( $failed instanceof MissingCommentConstraint ) {
$this->missingComment = true;
}
$statusValue = $failed->getLegacyStatus();
$status = Status::wrap( $statusValue );
return $status;
} else {
$constraintRunner->addConstraint(
new AutoSummaryMissingSummaryConstraint(
$this->summary,
$this->autoSumm,
$this->allowBlankSummary,
$content,
$this->getOriginalContent( $user )
)
);
}
// Check the constraints
if ( $constraintRunner->checkConstraints() === false ) {
$failed = $constraintRunner->getFailedConstraint();
if ( $failed instanceof EditFilterMergedContentHookConstraint ) {
$this->hookError = $failed->getHookError();
} elseif (
$failed instanceof AutoSummaryMissingSummaryConstraint ||
$failed instanceof NewSectionMissingSummaryConstraint
) {
$this->missingSummary = true;
} elseif ( $failed instanceof MissingCommentConstraint ) {
$this->missingComment = true;
}
// END OF MIGRATION TO EDITCONSTRAINT SYSTEM (continued below)
} elseif ( !$this->allowBlankSummary
&& !$content->equals( $this->getOriginalContent( $user ) )
&& !$content->isRedirect()
&& md5( $this->summary ) == $this->autoSumm
) {
$this->missingSummary = true;
$status->fatal( 'missingsummary' );
$status->value = self::AS_SUMMARY_NEEDED;
$statusValue = $failed->getLegacyStatus();
$status = Status::wrap( $statusValue );
return $status;
}
// END OF MIGRATION TO EDITCONSTRAINT SYSTEM (continued below)
# All's well
$sectionanchor = '';
@ -2354,26 +2348,20 @@ class EditPage implements IEditObject {
$status->value = self::AS_SUCCESS_UPDATE;
}
if ( !$this->allowSelfRedirect
&& $content->isRedirect()
&& $content->getRedirectTarget()->equals( $this->getTitle() )
) {
// If the page already redirects to itself, don't warn.
$currentTarget = $this->getCurrentContent()->getRedirectTarget();
if ( !$currentTarget || !$currentTarget->equals( $this->getTitle() ) ) {
$this->selfRedirect = true;
$status->fatal( 'selfredirect' );
$status->value = self::AS_SELF_REDIRECT;
return $status;
}
}
// Check for length errors again now that the section is merged in
$this->contentLength = strlen( $this->toEditText( $content ) );
// BEGINNING OF MIGRATION TO EDITCONSTRAINT SYSTEM (see T157658)
// Create a new runner to avoid rechecking the prior constraints, use the same factory
$constraintRunner = new EditConstraintRunner();
$constraintRunner->addConstraint(
new SelfRedirectConstraint(
$this->allowSelfRedirect,
$content,
$this->getCurrentContent(),
$this->getTitle()
)
);
$constraintRunner->addConstraint(
// Same constraint is used to check size before and after merging the
// edits, which use different failure codes
@ -2389,6 +2377,8 @@ class EditPage implements IEditObject {
if ( $failed instanceof PageSizeConstraint ) {
// Error will be displayed by showEditForm()
$this->tooBig = true;
} elseif ( $failed instanceof SelfRedirectConstraint ) {
$this->selfRedirect = true;
}
$statusValue = $failed->getLegacyStatus();

View file

@ -0,0 +1,106 @@
<?php
/**
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
*/
namespace MediaWiki\EditPage\Constraint;
use Content;
use StatusValue;
/**
* For an edit to an existing page but not with a new section, do not allow the user to post with
* a summary that matches the automatic summary if
* - the content has changed (to allow null edits without a summary, see T7365),
* - the new content is not a redirect (since redirecting a page has an informative automatic
* edit summary, see T9889), and
* - the user has not explicitely chosen to allow the automatic summary to be used
*
* For most edits, the automatic summary is blank, so checking against the automatic summary means
* checking that any summary was given.
*
* @since 1.36
* @internal
* @author DannyS712
*/
class AutoSummaryMissingSummaryConstraint implements IEditConstraint {
/** @var string */
private $userSummary;
/** @var string */
private $autoSummary;
/** @var bool */
private $allowBlankSummary;
/** @var Content */
private $newContent;
/** @var Content */
private $originalContent;
/** @var string|null */
private $result;
/**
* @param string $userSummary
* @param string $autoSummary
* @param bool $allowBlankSummary
* @param Content $newContent
* @param Content $originalContent
*/
public function __construct(
string $userSummary,
string $autoSummary,
bool $allowBlankSummary,
Content $newContent,
Content $originalContent
) {
$this->userSummary = $userSummary;
$this->autoSummary = $autoSummary;
$this->allowBlankSummary = $allowBlankSummary;
$this->newContent = $newContent;
$this->originalContent = $originalContent;
}
public function checkConstraint() : string {
if (
!$this->allowBlankSummary &&
!$this->newContent->equals( $this->originalContent ) &&
!$this->newContent->isRedirect() &&
md5( $this->userSummary ) == $this->autoSummary
) {
// TODO this was == in EditPage, can it be === ?
$this->result = self::CONSTRAINT_FAILED;
} else {
$this->result = self::CONSTRAINT_PASSED;
}
return $this->result;
}
public function getLegacyStatus() : StatusValue {
$statusValue = StatusValue::newGood();
if ( $this->result === self::CONSTRAINT_FAILED ) {
$statusValue->fatal( 'missingsummary' );
$statusValue->value = self::AS_SUMMARY_NEEDED;
}
return $statusValue;
}
}

View file

@ -0,0 +1,95 @@
<?php
/**
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
*/
namespace MediaWiki\EditPage\Constraint;
use Content;
use MediaWiki\Linker\LinkTarget;
use StatusValue;
/**
* Verify the page does not redirect to itself unless
* - the user is okay with a self redirect, or
* - the page already redirected to itself before the edit
*
* @since 1.36
* @internal
*/
class SelfRedirectConstraint implements IEditConstraint {
/** @var bool */
private $allowSelfRedirect;
/** @var Content */
private $newContent;
/** @var Content */
private $originalContent;
/** @var LinkTarget */
private $title;
/** @var string|null */
private $result;
/**
* @param bool $allowSelfRedirect
* @param Content $newContent
* @param Content $originalContent
* @param LinkTarget $title
*/
public function __construct(
bool $allowSelfRedirect,
Content $newContent,
Content $originalContent,
LinkTarget $title
) {
$this->allowSelfRedirect = $allowSelfRedirect;
$this->newContent = $newContent;
$this->originalContent = $originalContent;
$this->title = $title;
}
public function checkConstraint() : string {
if ( !$this->allowSelfRedirect
&& $this->newContent->isRedirect()
&& $this->newContent->getRedirectTarget()->equals( $this->title )
) {
// T29683 If the page already redirects to itself, don't warn.
$currentTarget = $this->originalContent->getRedirectTarget();
if ( !$currentTarget || !$currentTarget->equals( $this->title ) ) {
$this->result = self::CONSTRAINT_FAILED;
return self::CONSTRAINT_FAILED;
}
}
$this->result = self::CONSTRAINT_PASSED;
return self::CONSTRAINT_PASSED;
}
public function getLegacyStatus() : StatusValue {
$statusValue = StatusValue::newGood();
if ( $this->result === self::CONSTRAINT_FAILED ) {
$statusValue->fatal( 'selfredirect' );
$statusValue->value = self::AS_SELF_REDIRECT;
}
return $statusValue;
}
}

View file

@ -155,6 +155,38 @@ class EditPageConstraintsTest extends MediaWikiLangTestCase {
return WikiPage::factory( $title );
}
/** AutoSummaryMissingSummaryConstraint integration */
public function testAutoSummaryMissingSummaryConstraint() {
// Require the summary
$this->mergeMwGlobalArrayValue(
'wgDefaultUserOptions',
[ 'forceeditsummary' => 1 ]
);
$page = $this->getExistingTestPage( 'AutoSummaryMissingSummaryConstraint page does exist' );
$title = $page->getTitle();
$user = $this->getTestUser()->getUser();
$permissionManager = $this->getServiceContainer()->getPermissionManager();
// Needs edit rights to pass EditRightConstraint and reach NewSectionMissingSummaryConstraint
$permissionManager->overrideUserRightsForTesting( $user, [ 'edit' ] );
$edit = [
'wpTextbox1' => 'New content, different from base content',
'wpSummary' => 'SameAsAutoSummary',
'wpAutoSummary' => md5( 'SameAsAutoSummary' )
];
$this->assertEdit(
$title,
'Base content, different from new content',
$user,
$edit,
EditPage::AS_SUMMARY_NEEDED,
'expected AS_SUMMARY_NEEDED update'
);
}
/** ChangeTagsConstraint integration */
public function testChangeTagsConstraint() {
// Remove rights
@ -528,6 +560,26 @@ class EditPageConstraintsTest extends MediaWikiLangTestCase {
);
}
/** SelfRedirectConstraint integration */
public function testSelfRedirectConstraint() {
// Use a page that does not exist to be sure that it is not already a self redirect
$page = $this->getNonexistingTestPage( 'SelfRedirectConstraint page does not exist' );
$title = $page->getTitle();
$edit = [
'wpTextbox1' => '#REDIRECT [[SelfRedirectConstraint page does not exist]]',
'wpSummary' => 'Redirect to self'
];
$this->assertEdit(
$title,
'zero',
null,
$edit,
EditPage::AS_SELF_REDIRECT,
'expected AS_SELF_REDIRECT update'
);
}
/** SimpleAntiSpamConstraint integration */
public function testSimpleAntiSpamConstraint() {
$edit = [

View file

@ -16,6 +16,8 @@ class TalkPageNotificationManagerTest extends MediaWikiIntegrationTestCase {
protected function setUp(): void {
parent::setUp();
// tablesUsed don't clear up the database before the first test runs: T265033
$this->truncateTable( 'user_newtalk' );
$this->tablesUsed[] = 'user_newtalk';
}

View file

@ -0,0 +1,74 @@
<?php
/**
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
*/
use MediaWiki\EditPage\Constraint\AutoSummaryMissingSummaryConstraint;
use MediaWiki\EditPage\Constraint\IEditConstraint;
/**
* Tests the AutoSummaryMissingSummaryConstraint
*
* @author DannyS712
*
* @covers \MediaWiki\EditPage\Constraint\AutoSummaryMissingSummaryConstraint
*/
class AutoSummaryMissingSummaryConstraintTest extends MediaWikiUnitTestCase {
use EditConstraintTestTrait;
public function testPass() {
$originalContent = $this->createMock( Content::class );
$newContent = $this->createMock( Content::class );
$newContent->expects( $this->once() )
->method( 'equals' )
->with( $this->equalTo( $originalContent ) )
->willReturn( false );
$newContent->expects( $this->once() )
->method( 'isRedirect' )
->willReturn( false );
$constraint = new AutoSummaryMissingSummaryConstraint(
'UserSummary',
'AutoSummary',
false,
$newContent,
$originalContent
);
$this->assertConstraintPassed( $constraint );
}
public function testFailure() {
$originalContent = $this->createMock( Content::class );
$newContent = $this->createMock( Content::class );
$newContent->expects( $this->once() )
->method( 'equals' )
->with( $this->equalTo( $originalContent ) )
->willReturn( false );
$newContent->expects( $this->once() )
->method( 'isRedirect' )
->willReturn( false );
$constraint = new AutoSummaryMissingSummaryConstraint(
'UserSummary',
md5( 'UserSummary' ),
false,
$newContent,
$originalContent
);
$this->assertConstraintFailed( $constraint, IEditConstraint::AS_SUMMARY_NEEDED );
}
}

View file

@ -0,0 +1,77 @@
<?php
/**
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
*/
use MediaWiki\EditPage\Constraint\IEditConstraint;
use MediaWiki\EditPage\Constraint\SelfRedirectConstraint;
/**
* Tests the SelfRedirectConstraint
*
* @author DannyS712
*
* @covers \MediaWiki\EditPage\Constraint\SelfRedirectConstraint
*/
class SelfRedirectConstraintTest extends MediaWikiUnitTestCase {
use EditConstraintTestTrait;
private function getContent( $title, $isSelfRedirect ) {
$content = $this->createMock( Content::class );
$contentRedirectTarget = $this->createMock( Title::class );
// No $this->once() since only called for the new content
$content->method( 'isRedirect' )
->willReturn( true );
$content->expects( $this->once() )
->method( 'getRedirectTarget' )
->willReturn( $contentRedirectTarget );
$contentRedirectTarget->expects( $this->once() )
->method( 'equals' )
->with( $this->equalTo( $title ) )
->willReturn( $isSelfRedirect );
return $content;
}
public function testPass() {
// New content is a self redirect, but so is existing content, so no warning
$title = $this->createMock( Title::class );
$constraint = new SelfRedirectConstraint(
false, // $allowSelfRedirect
$this->getContent( $title, true ),
$this->getContent( $title, true ),
$title
);
$this->assertConstraintPassed( $constraint );
}
public function testFailure() {
// New content is a self redirect, but existing content is not
$title = $this->createMock( Title::class );
$constraint = new SelfRedirectConstraint(
false, // $allowSelfRedirect
$this->getContent( $title, true ),
$this->getContent( $title, false ),
$title
);
$this->assertConstraintFailed(
$constraint,
IEditConstraint::AS_SELF_REDIRECT
);
}
}