Mostly used find-and-replace: Find: /\*[\*\s]+@var (I?[A-Z](\w+)(?:Interface)?)[\s\*]+/\s*(private|protected|public) (\$[a-z]\w+;\n)((?=\s*/\*[\*\s]+@var (I?[A-Z](\w+)(?:Interface)?))\n|) Replace with: \3 \1 \4 More could be done, but to keep this patch reasonably sized, I only changed the most obvious and unambiguously correct cases. In some cases, I also removed redundant doc comments on the constructor, and re-ordered the properties to match the constructor. Change-Id: I819ed771c915293663856c577a481d607b76ed80
766 lines
23 KiB
PHP
766 lines
23 KiB
PHP
<?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\Block;
|
|
|
|
use ChangeTags;
|
|
use InvalidArgumentException;
|
|
use ManualLogEntry;
|
|
use MediaWiki\Block\Restriction\AbstractRestriction;
|
|
use MediaWiki\Block\Restriction\ActionRestriction;
|
|
use MediaWiki\Block\Restriction\NamespaceRestriction;
|
|
use MediaWiki\Block\Restriction\PageRestriction;
|
|
use MediaWiki\Config\ServiceOptions;
|
|
use MediaWiki\Deferred\DeferredUpdates;
|
|
use MediaWiki\HookContainer\HookContainer;
|
|
use MediaWiki\HookContainer\HookRunner;
|
|
use MediaWiki\MainConfigNames;
|
|
use MediaWiki\Message\Message;
|
|
use MediaWiki\Permissions\Authority;
|
|
use MediaWiki\Status\Status;
|
|
use MediaWiki\Title\MalformedTitleException;
|
|
use MediaWiki\Title\Title;
|
|
use MediaWiki\Title\TitleFactory;
|
|
use MediaWiki\User\UserEditTracker;
|
|
use MediaWiki\User\UserFactory;
|
|
use MediaWiki\User\UserIdentity;
|
|
use Psr\Log\LoggerInterface;
|
|
use RevisionDeleteUser;
|
|
use Wikimedia\ParamValidator\TypeDef\ExpiryDef;
|
|
|
|
/**
|
|
* Handles the backend logic of blocking users
|
|
*
|
|
* @since 1.36
|
|
*/
|
|
class BlockUser {
|
|
/**
|
|
* @var UserIdentity|string|null
|
|
*
|
|
* Target of the block
|
|
*
|
|
* This is null in case BlockUtils::parseBlockTarget failed to parse the target.
|
|
* Such case is detected in placeBlockUnsafe, by calling validateTarget from SpecialBlock.
|
|
*/
|
|
private $target;
|
|
|
|
/**
|
|
* @var int
|
|
*
|
|
* One of AbstractBlock::TYPE_* constants
|
|
*
|
|
* This will be -1 if BlockUtils::parseBlockTarget failed to parse the target.
|
|
*/
|
|
private $targetType;
|
|
|
|
/** @var Authority Performer of the block */
|
|
private $performer;
|
|
|
|
private ServiceOptions $options;
|
|
private BlockRestrictionStore $blockRestrictionStore;
|
|
private BlockPermissionChecker $blockPermissionChecker;
|
|
private BlockUtils $blockUtils;
|
|
private BlockActionInfo $blockActionInfo;
|
|
private HookRunner $hookRunner;
|
|
private DatabaseBlockStore $blockStore;
|
|
private UserFactory $userFactory;
|
|
private UserEditTracker $userEditTracker;
|
|
private LoggerInterface $logger;
|
|
private TitleFactory $titleFactory;
|
|
|
|
/**
|
|
* @internal For use by UserBlockCommandFactory
|
|
*/
|
|
public const CONSTRUCTOR_OPTIONS = [
|
|
MainConfigNames::HideUserContribLimit,
|
|
MainConfigNames::BlockAllowsUTEdit,
|
|
];
|
|
|
|
/**
|
|
* @var string
|
|
*
|
|
* Expiry of the to-be-placed block exactly as it was passed to the constructor.
|
|
*/
|
|
private $rawExpiry;
|
|
|
|
/**
|
|
* @var string|bool
|
|
*
|
|
* Parsed expiry. This may be false in case of an error in parsing.
|
|
*/
|
|
private $expiryTime;
|
|
|
|
/** @var string */
|
|
private $reason;
|
|
|
|
/** @var bool */
|
|
private $isCreateAccountBlocked = false;
|
|
|
|
/**
|
|
* @var bool|null
|
|
*
|
|
* This may be null when an invalid option was passed to the constructor.
|
|
* Such a case is caught in placeBlockUnsafe.
|
|
*/
|
|
private $isUserTalkEditBlocked = null;
|
|
|
|
/** @var bool */
|
|
private $isEmailBlocked = false;
|
|
|
|
/** @var bool */
|
|
private $isHardBlock = true;
|
|
|
|
/** @var bool */
|
|
private $isAutoblocking = true;
|
|
|
|
/** @var bool */
|
|
private $isHideUser = false;
|
|
|
|
/**
|
|
* @var bool
|
|
*
|
|
* Flag that needs to be true when the to-be-created block allows all editing,
|
|
* but does not allow some other action.
|
|
*
|
|
* This flag is used only by isPartial(), and should not be used anywhere else,
|
|
* even within this class. If you want to determine whether the block will be partial,
|
|
* use $this->isPartial().
|
|
*/
|
|
private $isPartialRaw;
|
|
|
|
/** @var AbstractRestriction[] */
|
|
private $blockRestrictions = [];
|
|
|
|
/** @var string[] */
|
|
private $tags = [];
|
|
|
|
/** @var int|null */
|
|
private $logDeletionFlags;
|
|
|
|
/**
|
|
* @param ServiceOptions $options
|
|
* @param BlockRestrictionStore $blockRestrictionStore
|
|
* @param BlockPermissionCheckerFactory $blockPermissionCheckerFactory
|
|
* @param BlockUtils $blockUtils
|
|
* @param BlockActionInfo $blockActionInfo
|
|
* @param HookContainer $hookContainer
|
|
* @param DatabaseBlockStore $databaseBlockStore
|
|
* @param UserFactory $userFactory
|
|
* @param UserEditTracker $userEditTracker
|
|
* @param LoggerInterface $logger
|
|
* @param TitleFactory $titleFactory
|
|
* @param string|UserIdentity $target Target of the block
|
|
* @param Authority $performer Performer of the block
|
|
* @param string $expiry Expiry of the block (timestamp or 'infinity')
|
|
* @param string $reason Reason of the block
|
|
* @param bool[] $blockOptions
|
|
* Valid options:
|
|
* - isCreateAccountBlocked : Are account creations prevented?
|
|
* - isEmailBlocked : Is emailing other users prevented?
|
|
* - isHardBlock : Are named (non-temporary) users prevented from editing?
|
|
* - isAutoblocking : Should this block spread to others to
|
|
* limit block evasion?
|
|
* - isUserTalkEditBlocked : Is editing blocked user's own talk page prevented?
|
|
* - isHideUser : Should blocked user's name be hidden (needs hideuser)?
|
|
* - isPartial : Is this block partial? This is ignored when
|
|
* blockRestrictions is not an empty array.
|
|
* @param AbstractRestriction[] $blockRestrictions
|
|
* @param string[] $tags Tags that should be assigned to the log entry
|
|
*/
|
|
public function __construct(
|
|
ServiceOptions $options,
|
|
BlockRestrictionStore $blockRestrictionStore,
|
|
BlockPermissionCheckerFactory $blockPermissionCheckerFactory,
|
|
BlockUtils $blockUtils,
|
|
BlockActionInfo $blockActionInfo,
|
|
HookContainer $hookContainer,
|
|
DatabaseBlockStore $databaseBlockStore,
|
|
UserFactory $userFactory,
|
|
UserEditTracker $userEditTracker,
|
|
LoggerInterface $logger,
|
|
TitleFactory $titleFactory,
|
|
$target,
|
|
Authority $performer,
|
|
string $expiry,
|
|
string $reason,
|
|
array $blockOptions,
|
|
array $blockRestrictions,
|
|
array $tags
|
|
) {
|
|
// Process dependencies
|
|
$options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
|
|
$this->options = $options;
|
|
$this->blockRestrictionStore = $blockRestrictionStore;
|
|
$this->blockPermissionChecker = $blockPermissionCheckerFactory
|
|
->newBlockPermissionChecker(
|
|
$target,
|
|
$performer
|
|
);
|
|
$this->blockUtils = $blockUtils;
|
|
$this->hookRunner = new HookRunner( $hookContainer );
|
|
$this->blockStore = $databaseBlockStore;
|
|
$this->userFactory = $userFactory;
|
|
$this->userEditTracker = $userEditTracker;
|
|
$this->logger = $logger;
|
|
$this->titleFactory = $titleFactory;
|
|
$this->blockActionInfo = $blockActionInfo;
|
|
|
|
// Process block target
|
|
[ $this->target, $rawTargetType ] = $this->blockUtils->parseBlockTarget( $target );
|
|
if ( $rawTargetType !== null ) { // Guard against invalid targets
|
|
$this->targetType = $rawTargetType;
|
|
} else {
|
|
$this->targetType = -1;
|
|
}
|
|
|
|
// Process other block parameters
|
|
$this->performer = $performer;
|
|
$this->rawExpiry = $expiry;
|
|
$this->expiryTime = self::parseExpiryInput( $this->rawExpiry );
|
|
$this->reason = $reason;
|
|
$this->blockRestrictions = $blockRestrictions;
|
|
$this->tags = $tags;
|
|
|
|
// Process blockOptions
|
|
foreach ( [
|
|
'isCreateAccountBlocked',
|
|
'isEmailBlocked',
|
|
'isHardBlock',
|
|
'isAutoblocking',
|
|
] as $possibleBlockOption ) {
|
|
if ( isset( $blockOptions[ $possibleBlockOption ] ) ) {
|
|
$this->$possibleBlockOption = $blockOptions[ $possibleBlockOption ];
|
|
}
|
|
}
|
|
|
|
$this->isPartialRaw = !empty( $blockOptions['isPartial'] ) && !$blockRestrictions;
|
|
|
|
if (
|
|
!$this->isPartial() ||
|
|
in_array( NS_USER_TALK, $this->getNamespaceRestrictions() )
|
|
) {
|
|
|
|
// It is possible to block user talk edit. User talk edit is:
|
|
// - always blocked if the config says so;
|
|
// - otherwise blocked/unblocked if the option was passed in;
|
|
// - otherwise defaults to not blocked.
|
|
if ( !$this->options->get( MainConfigNames::BlockAllowsUTEdit ) ) {
|
|
$this->isUserTalkEditBlocked = true;
|
|
} else {
|
|
$this->isUserTalkEditBlocked = $blockOptions['isUserTalkEditBlocked'] ?? false;
|
|
}
|
|
|
|
} else {
|
|
|
|
// It is not possible to block user talk edit. If the option
|
|
// was passed, an error will be thrown in ::placeBlockUnsafe.
|
|
// Otherwise, set to not blocked.
|
|
if ( !isset( $blockOptions['isUserTalkEditBlocked'] ) || !$blockOptions['isUserTalkEditBlocked'] ) {
|
|
$this->isUserTalkEditBlocked = false;
|
|
}
|
|
|
|
}
|
|
|
|
if (
|
|
isset( $blockOptions['isHideUser'] ) &&
|
|
$this->targetType === AbstractBlock::TYPE_USER
|
|
) {
|
|
$this->isHideUser = $blockOptions['isHideUser'];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @unstable This method might be removed without prior notice (see T271101)
|
|
* @param int $flags One of LogPage::* constants
|
|
*/
|
|
public function setLogDeletionFlags( int $flags ): void {
|
|
$this->logDeletionFlags = $flags;
|
|
}
|
|
|
|
/**
|
|
* Convert a submitted expiry time, which may be relative ("2 weeks", etc) or absolute
|
|
* ("24 May 2034", etc), into an absolute timestamp we can put into the database.
|
|
*
|
|
* @todo strtotime() only accepts English strings. This means the expiry input
|
|
* can only be specified in English.
|
|
* @see https://www.php.net/manual/en/function.strtotime.php
|
|
*
|
|
* @param string $expiry Whatever was typed into the form
|
|
*
|
|
* @return string|false Timestamp (format TS_MW) or 'infinity' or false on error.
|
|
*/
|
|
public static function parseExpiryInput( string $expiry ) {
|
|
try {
|
|
return ExpiryDef::normalizeExpiry( $expiry, TS_MW );
|
|
} catch ( InvalidArgumentException $e ) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Is the to-be-placed block partial?
|
|
*
|
|
* @return bool
|
|
*/
|
|
private function isPartial(): bool {
|
|
return $this->blockRestrictions !== [] || $this->isPartialRaw;
|
|
}
|
|
|
|
/**
|
|
* Configure DatabaseBlock according to class properties
|
|
*
|
|
* @param DatabaseBlock|null $sourceBlock Copy any options from this block.
|
|
* Null to construct a new one.
|
|
*
|
|
* @return DatabaseBlock
|
|
*/
|
|
private function configureBlock( $sourceBlock = null ): DatabaseBlock {
|
|
if ( $sourceBlock === null ) {
|
|
$block = new DatabaseBlock();
|
|
} else {
|
|
$block = clone $sourceBlock;
|
|
}
|
|
|
|
$isSitewide = !$this->isPartial();
|
|
|
|
$block->setTarget( $this->target );
|
|
$block->setBlocker( $this->performer->getUser() );
|
|
$block->setReason( $this->reason );
|
|
$block->setExpiry( $this->expiryTime );
|
|
$block->isCreateAccountBlocked( $this->isCreateAccountBlocked );
|
|
$block->isEmailBlocked( $this->isEmailBlocked );
|
|
$block->isHardblock( $this->isHardBlock );
|
|
$block->isAutoblocking( $this->isAutoblocking );
|
|
$block->isSitewide( $isSitewide );
|
|
$block->isUsertalkEditAllowed( !$this->isUserTalkEditBlocked );
|
|
$block->setHideName( $this->isHideUser );
|
|
|
|
$blockId = $block->getId();
|
|
if ( $blockId === null ) {
|
|
// Block wasn't inserted into the DB yet
|
|
$block->setRestrictions( $this->blockRestrictions );
|
|
} else {
|
|
// Block is in the DB, we need to set restrictions through a service
|
|
$block->setRestrictions(
|
|
$this->blockRestrictionStore->setBlockId(
|
|
$blockId,
|
|
$this->blockRestrictions
|
|
)
|
|
);
|
|
}
|
|
|
|
return $block;
|
|
}
|
|
|
|
/**
|
|
* Place a block, checking permissions
|
|
*
|
|
* @param bool $reblock Should this reblock?
|
|
*
|
|
* @return Status If the block is successful, the value of the returned
|
|
* Status is an instance of a newly placed block.
|
|
*/
|
|
public function placeBlock( bool $reblock = false ): Status {
|
|
$priorBlock = $this->blockStore
|
|
->newFromTarget( $this->target, null, /*fromPrimary=*/true );
|
|
$priorHideUser = $priorBlock instanceof DatabaseBlock && $priorBlock->getHideName();
|
|
if (
|
|
$this->blockPermissionChecker
|
|
->checkBasePermissions(
|
|
$this->isHideUser || $priorHideUser
|
|
) !== true
|
|
) {
|
|
$this->logger->debug( 'placeBlock: checkBasePermissions failed' );
|
|
return Status::newFatal( $priorHideUser ? 'cant-see-hidden-user' : 'badaccess-group0' );
|
|
}
|
|
|
|
$blockCheckResult = $this->blockPermissionChecker->checkBlockPermissions();
|
|
if ( $blockCheckResult !== true ) {
|
|
$this->logger->debug( 'placeBlock: checkBlockPermissions failed' );
|
|
return Status::newFatal( $blockCheckResult );
|
|
}
|
|
|
|
if (
|
|
$this->isEmailBlocked &&
|
|
!$this->blockPermissionChecker->checkEmailPermissions()
|
|
) {
|
|
// TODO: Maybe not ignore the error here?
|
|
$this->isEmailBlocked = false;
|
|
}
|
|
|
|
if ( $this->tags !== [] ) {
|
|
$status = ChangeTags::canAddTagsAccompanyingChange(
|
|
$this->tags,
|
|
$this->performer
|
|
);
|
|
|
|
if ( !$status->isOK() ) {
|
|
$this->logger->debug( 'placeBlock: ChangeTags::canAddTagsAccompanyingChange failed' );
|
|
return $status;
|
|
}
|
|
}
|
|
|
|
$status = Status::newGood();
|
|
foreach ( $this->getPageRestrictions() as $pageRestriction ) {
|
|
try {
|
|
$title = $this->titleFactory->newFromTextThrow( $pageRestriction );
|
|
if ( !$title->exists() ) {
|
|
$this->logger->debug( "placeBlock: nonexistent page restriction $title" );
|
|
$status->fatal( 'cant-block-nonexistent-page', $pageRestriction );
|
|
}
|
|
} catch ( MalformedTitleException $e ) {
|
|
$this->logger->debug( 'placeBlock: malformed page restriction title' );
|
|
$status->fatal( $e->getMessageObject() );
|
|
}
|
|
}
|
|
if ( !$status->isOK() ) {
|
|
return $status;
|
|
}
|
|
|
|
return $this->placeBlockUnsafe( $reblock );
|
|
}
|
|
|
|
/**
|
|
* Place a block without any sort of permissions checks.
|
|
*
|
|
* @param bool $reblock Should this reblock?
|
|
*
|
|
* @return Status If the block is successful, the value of the returned
|
|
* Status is an instance of a newly placed block.
|
|
*/
|
|
public function placeBlockUnsafe( bool $reblock = false ): Status {
|
|
$status = $this->blockUtils->validateTarget( $this->target );
|
|
|
|
if ( !$status->isOK() ) {
|
|
$this->logger->debug( 'placeBlockUnsafe: invalid target' );
|
|
return $status;
|
|
}
|
|
|
|
if ( $this->isUserTalkEditBlocked === null ) {
|
|
$this->logger->debug( 'placeBlockUnsafe: partial block on user talk page' );
|
|
return Status::newFatal( 'ipb-prevent-user-talk-edit' );
|
|
}
|
|
|
|
if (
|
|
// There should be some expiry
|
|
strlen( $this->rawExpiry ) === 0 ||
|
|
// can't be a larger string as 50 (it should be a time format in any way)
|
|
strlen( $this->rawExpiry ) > 50 ||
|
|
// the time can't be parsed
|
|
!$this->expiryTime
|
|
) {
|
|
$this->logger->debug( 'placeBlockUnsafe: invalid expiry' );
|
|
return Status::newFatal( 'ipb_expiry_invalid' );
|
|
}
|
|
|
|
if ( $this->expiryTime < wfTimestampNow() ) {
|
|
$this->logger->debug( 'placeBlockUnsafe: expiry in the past' );
|
|
return Status::newFatal( 'ipb_expiry_old' );
|
|
}
|
|
|
|
if ( $this->isHideUser ) {
|
|
if ( $this->isPartial() ) {
|
|
$this->logger->debug( 'placeBlockUnsafe: partial block cannot hide user' );
|
|
return Status::newFatal( 'ipb_hide_partial' );
|
|
}
|
|
|
|
if ( !wfIsInfinity( $this->rawExpiry ) ) {
|
|
$this->logger->debug( 'placeBlockUnsafe: temp user block has expiry' );
|
|
return Status::newFatal( 'ipb_expiry_temp' );
|
|
}
|
|
|
|
$hideUserContribLimit = $this->options->get( MainConfigNames::HideUserContribLimit );
|
|
if (
|
|
$hideUserContribLimit !== false &&
|
|
$this->userEditTracker->getUserEditCount( $this->target ) > $hideUserContribLimit
|
|
) {
|
|
$this->logger->debug( 'placeBlockUnsafe: hide user with too many contribs' );
|
|
return Status::newFatal( 'ipb_hide_invalid', Message::numParam( $hideUserContribLimit ) );
|
|
}
|
|
}
|
|
|
|
if ( $this->isPartial() ) {
|
|
if (
|
|
$this->blockRestrictions === [] &&
|
|
!$this->isEmailBlocked &&
|
|
!$this->isCreateAccountBlocked &&
|
|
!$this->isUserTalkEditBlocked
|
|
) {
|
|
$this->logger->debug( 'placeBlockUnsafe: empty partial block' );
|
|
return Status::newFatal( 'ipb-empty-block' );
|
|
}
|
|
}
|
|
|
|
return $this->placeBlockInternal( $reblock );
|
|
}
|
|
|
|
/**
|
|
* Places a block without any sort of permission or double checking, hooks can still
|
|
* abort the block through, as well as already existing block.
|
|
*
|
|
* @param bool $reblock Should this reblock?
|
|
*
|
|
* @return Status
|
|
*/
|
|
private function placeBlockInternal( bool $reblock = true ): Status {
|
|
$block = $this->configureBlock();
|
|
|
|
$denyReason = [ 'hookaborted' ];
|
|
$legacyUser = $this->userFactory->newFromAuthority( $this->performer );
|
|
if ( !$this->hookRunner->onBlockIp( $block, $legacyUser, $denyReason ) ) {
|
|
$status = Status::newGood();
|
|
foreach ( $denyReason as $key ) {
|
|
$this->logger->debug( "placeBlockInternal: hook aborted with message \"$key\"" );
|
|
$status->fatal( $key );
|
|
}
|
|
return $status;
|
|
}
|
|
|
|
// Is there a conflicting block?
|
|
// xxx: there is an identical call at the beginning of ::placeBlock
|
|
$priorBlock = $this->blockStore
|
|
->newFromTarget( $this->target, null, /*fromPrimary=*/true );
|
|
|
|
// T287798: we are blocking an IP that is currently autoblocked
|
|
// we can ignore the block because ipb_address_unique allows the IP address
|
|
// be both manually blocked and autoblocked
|
|
// this will work as long as DatabaseBlockStore::newLoad prefers manual IP blocks
|
|
// over autoblocks
|
|
if ( $priorBlock !== null
|
|
&& $priorBlock->getType() === AbstractBlock::TYPE_AUTO
|
|
&& $this->targetType === AbstractBlock::TYPE_IP
|
|
) {
|
|
$priorBlock = null;
|
|
}
|
|
|
|
if ( $priorBlock !== null ) {
|
|
// Reblock only if the caller wants so
|
|
if ( !$reblock ) {
|
|
$this->logger->debug(
|
|
'placeBlockInternal: already blocked and reblock not requested' );
|
|
return Status::newFatal( 'ipb_already_blocked', $block->getTargetName() );
|
|
}
|
|
|
|
if ( $block->equals( $priorBlock ) ) {
|
|
// Block settings are equal => user is already blocked
|
|
$this->logger->debug( 'placeBlockInternal: already blocked, no change' );
|
|
return Status::newFatal( 'ipb_already_blocked', $block->getTargetName() );
|
|
}
|
|
|
|
$currentBlock = $this->configureBlock( $priorBlock );
|
|
$logEntry = $this->prepareLogEntry( true );
|
|
$this->blockStore->updateBlock( $currentBlock ); // TODO handle failure
|
|
$block = $currentBlock;
|
|
} else {
|
|
$logEntry = $this->prepareLogEntry( false );
|
|
// Try to insert block.
|
|
$insertStatus = $this->blockStore->insertBlock( $block );
|
|
if ( !$insertStatus ) {
|
|
$this->logger->warning( 'Block could not be inserted. No existing block was found.' );
|
|
return Status::newFatal( 'ipb-block-not-found', $block->getTargetName() );
|
|
}
|
|
}
|
|
// Relate log ID to block ID (T27763)
|
|
$logEntry->setRelations( [ 'ipb_id' => $block->getId() ] );
|
|
|
|
// Set *_deleted fields if requested
|
|
if ( $this->isHideUser ) {
|
|
// This should only be the case of $this->target is a user, so we can
|
|
// safely call ->getId()
|
|
RevisionDeleteUser::suppressUserName( $this->target->getName(), $this->target->getId() );
|
|
}
|
|
|
|
DeferredUpdates::addCallableUpdate( function () use ( $block, $legacyUser, $priorBlock ) {
|
|
$this->hookRunner->onBlockIpComplete( $block, $legacyUser, $priorBlock );
|
|
} );
|
|
|
|
// DatabaseBlock constructor sanitizes certain block options on insert
|
|
$this->isEmailBlocked = $block->isEmailBlocked();
|
|
$this->isAutoblocking = $block->isAutoblocking();
|
|
|
|
$this->log( $logEntry );
|
|
|
|
$this->logger->debug( 'placeBlockInternal: success' );
|
|
return Status::newGood( $block );
|
|
}
|
|
|
|
/**
|
|
* Build namespace restrictions array from $this->blockRestrictions
|
|
*
|
|
* Returns an array of namespace IDs.
|
|
*
|
|
* @return int[]
|
|
*/
|
|
private function getNamespaceRestrictions(): array {
|
|
$namespaceRestrictions = [];
|
|
foreach ( $this->blockRestrictions as $restriction ) {
|
|
if ( $restriction instanceof NamespaceRestriction ) {
|
|
$namespaceRestrictions[] = $restriction->getValue();
|
|
}
|
|
}
|
|
return $namespaceRestrictions;
|
|
}
|
|
|
|
/**
|
|
* Build an array of page restrictions from $this->blockRestrictions
|
|
*
|
|
* Returns an array of stringified full page titles.
|
|
*
|
|
* @return string[]
|
|
*/
|
|
private function getPageRestrictions(): array {
|
|
$pageRestrictions = [];
|
|
foreach ( $this->blockRestrictions as $restriction ) {
|
|
if ( $restriction instanceof PageRestriction ) {
|
|
$pageRestrictions[] = $restriction->getTitle()->getFullText();
|
|
}
|
|
}
|
|
return $pageRestrictions;
|
|
}
|
|
|
|
/**
|
|
* Build an array of actions from $this->blockRestrictions
|
|
*
|
|
* Returns an array of stringified actions.
|
|
*
|
|
* @return string[]
|
|
*/
|
|
private function getActionRestrictions(): array {
|
|
$actionRestrictions = [];
|
|
foreach ( $this->blockRestrictions as $restriction ) {
|
|
if ( $restriction instanceof ActionRestriction ) {
|
|
$actionRestrictions[] = $this->blockActionInfo->getActionFromId( $restriction->getValue() );
|
|
}
|
|
}
|
|
return $actionRestrictions;
|
|
}
|
|
|
|
/**
|
|
* Prepare $logParams
|
|
*
|
|
* Helper method for $this->log()
|
|
*
|
|
* @return array
|
|
*/
|
|
private function constructLogParams(): array {
|
|
$logExpiry = wfIsInfinity( $this->rawExpiry ) ? 'infinity' : $this->rawExpiry;
|
|
$logParams = [
|
|
'5::duration' => $logExpiry,
|
|
'6::flags' => $this->blockLogFlags(),
|
|
'sitewide' => !$this->isPartial()
|
|
];
|
|
|
|
if ( $this->isPartial() ) {
|
|
$pageRestrictions = $this->getPageRestrictions();
|
|
$namespaceRestrictions = $this->getNamespaceRestrictions();
|
|
$actionRestrictions = $this->getActionRestrictions();
|
|
|
|
if ( count( $pageRestrictions ) > 0 ) {
|
|
$logParams['7::restrictions']['pages'] = $pageRestrictions;
|
|
}
|
|
if ( count( $namespaceRestrictions ) > 0 ) {
|
|
$logParams['7::restrictions']['namespaces'] = $namespaceRestrictions;
|
|
}
|
|
if ( count( $actionRestrictions ) ) {
|
|
$logParams['7::restrictions']['actions'] = $actionRestrictions;
|
|
}
|
|
}
|
|
return $logParams;
|
|
}
|
|
|
|
/**
|
|
* Create the log entry object to be inserted. Do read queries here before
|
|
* we start locking block_target rows.
|
|
*
|
|
* @param bool $isReblock
|
|
* @return ManualLogEntry
|
|
*/
|
|
private function prepareLogEntry( bool $isReblock ) {
|
|
$logType = $this->isHideUser ? 'suppress' : 'block';
|
|
$logAction = $isReblock ? 'reblock' : 'block';
|
|
$title = Title::makeTitle( NS_USER, $this->target );
|
|
// Preload the page_id: needed for log_page in ManualLogEntry::insert()
|
|
$title->getArticleID();
|
|
|
|
$logEntry = new ManualLogEntry( $logType, $logAction );
|
|
$logEntry->setTarget( $title );
|
|
$logEntry->setComment( $this->reason );
|
|
$logEntry->setPerformer( $this->performer->getUser() );
|
|
$logEntry->setParameters( $this->constructLogParams() );
|
|
$logEntry->addTags( $this->tags );
|
|
if ( $this->logDeletionFlags !== null ) {
|
|
$logEntry->setDeleted( $this->logDeletionFlags );
|
|
}
|
|
return $logEntry;
|
|
}
|
|
|
|
/**
|
|
* Log the block to Special:Log
|
|
*
|
|
* @param ManualLogEntry $logEntry
|
|
*/
|
|
private function log( ManualLogEntry $logEntry ) {
|
|
$logId = $logEntry->insert();
|
|
$logEntry->publish( $logId );
|
|
}
|
|
|
|
/**
|
|
* Return a comma-delimited list of flags to be passed to the log
|
|
* reader for this block, to provide more information in the logs.
|
|
*
|
|
* @return string
|
|
*/
|
|
private function blockLogFlags(): string {
|
|
$flags = [];
|
|
|
|
if ( $this->targetType != AbstractBlock::TYPE_USER && !$this->isHardBlock ) {
|
|
// For grepping: message block-log-flags-anononly
|
|
$flags[] = 'anononly';
|
|
}
|
|
|
|
if ( $this->isCreateAccountBlocked ) {
|
|
// For grepping: message block-log-flags-nocreate
|
|
$flags[] = 'nocreate';
|
|
}
|
|
|
|
if ( $this->targetType == AbstractBlock::TYPE_USER && !$this->isAutoblocking ) {
|
|
// For grepping: message block-log-flags-noautoblock
|
|
$flags[] = 'noautoblock';
|
|
}
|
|
|
|
if ( $this->isEmailBlocked ) {
|
|
// For grepping: message block-log-flags-noemail
|
|
$flags[] = 'noemail';
|
|
}
|
|
|
|
if ( $this->options->get( MainConfigNames::BlockAllowsUTEdit ) && $this->isUserTalkEditBlocked ) {
|
|
// For grepping: message block-log-flags-nousertalk
|
|
$flags[] = 'nousertalk';
|
|
}
|
|
|
|
if ( $this->isHideUser ) {
|
|
// For grepping: message block-log-flags-hiddenname
|
|
$flags[] = 'hiddenname';
|
|
}
|
|
|
|
return implode( ',', $flags );
|
|
}
|
|
}
|