use Wikimedia\Rdbms\Database;
use Wikimedia\Rdbms\IDatabase;
+use MediaWiki\Block\BlockRestriction;
+use MediaWiki\Block\Restriction\Restriction;
use MediaWiki\MediaWikiServices;
class Block {
/** @var string|null */
private $systemBlockType;
+ /** @var bool */
+ private $isSitewide;
+
+ /** @var Restriction[] */
+ private $restrictions;
+
# TYPE constants
const TYPE_USER = 1;
const TYPE_IP = 2;
'allowUsertalk' => false,
'byText' => '',
'systemBlock' => null,
+ 'sitewide' => true,
];
if ( func_num_args() > 1 || !is_array( $options ) ) {
$this->mHideName = (bool)$options['hideName'];
$this->isHardblock( !$options['anonOnly'] );
$this->isAutoblocking( (bool)$options['enableAutoblock'] );
+ $this->isSitewide( (bool)$options['sitewide'] );
# Prevention measures
$this->prevents( 'sendemail', (bool)$options['blockEmail'] );
public static function selectFields() {
global $wgActorTableSchemaMigrationStage;
- if ( $wgActorTableSchemaMigrationStage > MIGRATION_WRITE_BOTH ) {
+ if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) {
// If code is using this instead of self::getQueryInfo(), there's a
// decent chance it's going to try to directly access
// $row->ipb_by or $row->ipb_by_text and we can't give it
- // useful values here once those aren't being written anymore.
+ // useful values here once those aren't being used anymore.
throw new BadMethodCallException(
- 'Cannot use ' . __METHOD__ . ' when $wgActorTableSchemaMigrationStage > MIGRATION_WRITE_BOTH'
+ 'Cannot use ' . __METHOD__
+ . ' when $wgActorTableSchemaMigrationStage has SCHEMA_COMPAT_READ_NEW'
);
}
'ipb_address',
'ipb_by',
'ipb_by_text',
- 'ipb_by_actor' => $wgActorTableSchemaMigrationStage > MIGRATION_OLD ? 'ipb_by_actor' : 'NULL',
+ 'ipb_by_actor' => 'NULL',
'ipb_timestamp',
'ipb_auto',
'ipb_anon_only',
'ipb_block_email',
'ipb_allow_usertalk',
'ipb_parent_block_id',
+ 'ipb_sitewide',
] + CommentStore::getStore()->getFields( 'ipb_reason' );
}
'ipb_block_email',
'ipb_allow_usertalk',
'ipb_parent_block_id',
+ 'ipb_sitewide',
] + $commentQuery['fields'] + $actorQuery['fields'],
'joins' => $commentQuery['joins'] + $actorQuery['joins'],
];
&& $this->prevents( 'sendemail' ) == $block->prevents( 'sendemail' )
&& $this->prevents( 'editownusertalk' ) == $block->prevents( 'editownusertalk' )
&& $this->mReason == $block->mReason
+ && $this->isSitewide() == $block->isSitewide()
+ // Block::getRestrictions() may perform a database query, so keep it at
+ // the end.
+ && BlockRestriction::equals( $this->getRestrictions(), $block->getRestrictions() )
);
}
$this->isHardblock( !$row->ipb_anon_only );
$this->isAutoblocking( $row->ipb_enable_autoblock );
+ $this->isSitewide( (bool)$row->ipb_sitewide );
$this->prevents( 'createaccount', $row->ipb_create_account );
$this->prevents( 'sendemail', $row->ipb_block_email );
}
$dbw = wfGetDB( DB_MASTER );
+
+ BlockRestriction::deleteByParentBlockId( $this->getId() );
$dbw->delete( 'ipblocks', [ 'ipb_parent_block_id' => $this->getId() ], __METHOD__ );
+
+ BlockRestriction::deleteByBlockId( $this->getId() );
$dbw->delete( 'ipblocks', [ 'ipb_id' => $this->getId() ], __METHOD__ );
return $dbw->affectedRows() > 0;
$dbw->insert( 'ipblocks', $row, __METHOD__, [ 'IGNORE' ] );
$affected = $dbw->affectedRows();
- $this->mId = $dbw->insertId();
+ if ( $affected ) {
+ $this->setId( $dbw->insertId() );
+ if ( $this->restrictions ) {
+ BlockRestriction::insert( $this->restrictions );
+ }
+ }
# Don't collide with expired blocks.
# Do this after trying to insert to avoid locking.
);
if ( $ids ) {
$dbw->delete( 'ipblocks', [ 'ipb_id' => $ids ], __METHOD__ );
+ BlockRestriction::deleteByBlockId( $ids );
$dbw->insert( 'ipblocks', $row, __METHOD__, [ 'IGNORE' ] );
$affected = $dbw->affectedRows();
- $this->mId = $dbw->insertId();
+ $this->setId( $dbw->insertId() );
+ if ( $this->restrictions ) {
+ BlockRestriction::insert( $this->restrictions );
+ }
}
}
$dbw->startAtomic( __METHOD__ );
- $dbw->update(
+ $result = $dbw->update(
'ipblocks',
$this->getDatabaseArray( $dbw ),
[ 'ipb_id' => $this->getId() ],
__METHOD__
);
- $affected = $dbw->affectedRows();
+ // Only update the restrictions if they have been modified.
+ if ( $this->restrictions !== null ) {
+ // An empty array should remove all of the restrictions.
+ if ( empty( $this->restrictions ) ) {
+ $success = BlockRestriction::deleteByBlockId( $this->getId() );
+ } else {
+ $success = BlockRestriction::update( $this->restrictions );
+ }
+ // Update the result. The first false is the result, otherwise, true.
+ $result = $result && $success;
+ }
if ( $this->isAutoblocking() ) {
// update corresponding autoblock(s) (T50813)
[ 'ipb_parent_block_id' => $this->getId() ],
__METHOD__
);
+
+ // Only update the restrictions if they have been modified.
+ if ( $this->restrictions !== null ) {
+ BlockRestriction::updateByParentBlockId( $this->getId(), $this->restrictions );
+ }
} else {
// autoblock no longer required, delete corresponding autoblock(s)
+ BlockRestriction::deleteByParentBlockId( $this->getId() );
$dbw->delete(
'ipblocks',
[ 'ipb_parent_block_id' => $this->getId() ],
$dbw->endAtomic( __METHOD__ );
- if ( $affected ) {
+ if ( $result ) {
$auto_ipd_ids = $this->doRetroactiveAutoblock();
return [ 'id' => $this->mId, 'autoIds' => $auto_ipd_ids ];
}
- return false;
+ return $result;
}
/**
'ipb_deleted' => intval( $this->mHideName ), // typecast required for SQLite
'ipb_block_email' => $this->prevents( 'sendemail' ),
'ipb_allow_usertalk' => !$this->prevents( 'editownusertalk' ),
- 'ipb_parent_block_id' => $this->mParentBlockId
+ 'ipb_parent_block_id' => $this->mParentBlockId,
+ 'ipb_sitewide' => $this->isSitewide(),
] + CommentStore::getStore()->insert( $dbw, 'ipb_reason', $this->mReason )
+ ActorMigration::newMigration()->getInsertValues( $dbw, 'ipb_by', $this->getBlocker() );
$autoblock->mHideName = $this->mHideName;
$autoblock->prevents( 'editownusertalk', $this->prevents( 'editownusertalk' ) );
$autoblock->mParentBlockId = $this->mId;
+ $autoblock->isSitewide( $this->isSitewide() );
+ $autoblock->setRestrictions( $this->getRestrictions() );
if ( $this->mExpiry == 'infinity' ) {
# Original block was indefinite, start an autoblock now
return $this->mId;
}
+ /**
+ * Set the block ID
+ *
+ * @param int $blockId
+ * @return int
+ */
+ private function setId( $blockId ) {
+ $this->mId = (int)$blockId;
+
+ if ( is_array( $this->restrictions ) ) {
+ $this->restrictions = BlockRestriction::setBlockId( $blockId, $this->restrictions );
+ }
+
+ return $this;
+ }
+
/**
* Get the system block type, if any
* @since 1.29
: false;
}
+ /**
+ * Indicates that the block is a sitewide block. This means the user is
+ * prohibited from editing any page on the site (other than their own talk
+ * page).
+ *
+ * @param null|bool $x
+ * @return bool
+ */
+ public function isSitewide( $x = null ) {
+ return wfSetVar( $this->isSitewide, $x );
+ }
+
/**
* Get/set whether the Block prevents a given action
*
* @return bool|null Null for unrecognized rights.
*/
public function prevents( $action, $x = null ) {
- global $wgBlockDisablesLogin;
+ $config = RequestContext::getMain()->getConfig();
+ $blockDisablesLogin = $config->get( 'BlockDisablesLogin' );
+ $blockAllowsUTEdit = $config->get( 'BlockAllowsUTEdit' );
+
$res = null;
switch ( $action ) {
case 'edit':
case 'sendemail':
$res = wfSetVar( $this->mBlockEmail, $x );
break;
+ case 'upload':
+ // Until T6995 is completed
+ $res = $this->isSitewide();
+ break;
case 'editownusertalk':
$res = wfSetVar( $this->mDisableUsertalk, $x );
+ // edit own user talk can be disabled by config
+ if ( !$blockAllowsUTEdit ) {
+ $res = true;
+ }
break;
case 'read':
$res = false;
break;
}
- if ( !$res && $wgBlockDisablesLogin ) {
+ if ( !$res && $blockDisablesLogin ) {
// If a block would disable login, then it should
// prevent any action that all users cannot do
$anon = new User;
$fname
);
if ( $ids ) {
+ BlockRestriction::deleteByBlockId( $ids );
$dbw->delete( 'ipblocks', [ 'ipb_id' => $ids ], $fname );
}
}
}
# Consider the possibility that this is not a username at all
- # but actually an old subpage (bug #29797)
+ # but actually an old subpage (T31797)
if ( strpos( $target, '/' ) !== false ) {
# An old subpage, drill down to the user behind it
$target = explode( '/', $target )[0];
* @return array
*/
public function getPermissionsError( IContextSource $context ) {
+ $params = $this->getBlockErrorParams( $context );
+
+ $msg = 'blockedtext';
+ if ( $this->getSystemBlockType() !== null ) {
+ $msg = 'systemblockedtext';
+ } elseif ( $this->mAuto ) {
+ $msg = 'autoblockedtext';
+ } elseif ( !$this->isSitewide() ) {
+ $msg = 'blockedtext-partial';
+ }
+
+ array_unshift( $params, $msg );
+
+ return $params;
+ }
+
+ /**
+ * Get block information used in different block error messages
+ *
+ * @param IContextSource $context
+ * @return array
+ */
+ public function getBlockErrorParams( IContextSource $context ) {
$blocker = $this->getBlocker();
if ( $blocker instanceof User ) { // local user
$blockerUserpage = $blocker->getUserPage();
/* $ip returns who *is* being blocked, $intended contains who was meant to be blocked.
* This could be a username, an IP range, or a single IP. */
$intended = $this->getTarget();
-
$systemBlockType = $this->getSystemBlockType();
-
$lang = $context->getLanguage();
+
return [
- $systemBlockType !== null
- ? 'systemblockedtext'
- : ( $this->mAuto ? 'autoblockedtext' : 'blockedtext' ),
$link,
$reason,
$context->getRequest()->getIP(),
$lang->userTimeAndDate( $this->mTimestamp, $context->getUser() ),
];
}
+
+ /**
+ * Get Restrictions.
+ *
+ * Getting the restrictions will perform a database query if the restrictions
+ * are not already loaded.
+ *
+ * @return Restriction[]
+ */
+ public function getRestrictions() {
+ if ( $this->restrictions === null ) {
+ // If the block id has not been set, then do not attempt to load the
+ // restrictions.
+ if ( !$this->mId ) {
+ return [];
+ }
+ $this->restrictions = BlockRestriction::loadByBlockId( $this->mId );
+ }
+
+ return $this->restrictions;
+ }
+
+ /**
+ * Set Restrictions.
+ *
+ * @param Restriction[] $restrictions
+ *
+ * @return self
+ */
+ public function setRestrictions( array $restrictions ) {
+ $this->restrictions = array_filter( $restrictions, function ( $restriction ) {
+ return $restriction instanceof Restriction;
+ } );
+
+ return $this;
+ }
+
+ /**
+ * Checks if a block prevents an edit on a given article
+ *
+ * @param \Title $title
+ * @return bool
+ */
+ public function preventsEdit( \Title $title ) {
+ $blocked = $this->isSitewide();
+
+ // user talk page has it's own rules
+ // This check happens before partial blocks because the flag
+ // to allow user to edit their user talk page could be
+ // overwritten by a partial block restriction (E.g. user talk namespace)
+ $user = $this->getTarget();
+ if ( $title->equals( $user->getTalkPage() ) ) {
+ $blocked = $this->prevents( 'editownusertalk' );
+ }
+
+ if ( !$this->isSitewide() ) {
+ $restrictions = $this->getRestrictions();
+ foreach ( $restrictions as $restriction ) {
+ if ( $restriction->matches( $title ) ) {
+ $blocked = true;
+ }
+ }
+ }
+
+ return $blocked;
+ }
}