use AutoCommitUpdate;
use BadMethodCallException;
use CommentStore;
-use DateTime;
use DeferredUpdates;
use Hooks;
use Html;
use MediaWiki\Block\Restriction\PageRestriction;
use MediaWiki\Block\Restriction\Restriction;
use MediaWiki\MediaWikiServices;
-use MWCryptHash;
use MWException;
use RequestContext;
use stdClass;
}
/**
- * Load a block from the database which affects the already-set $this->target:
- * 1) A block directly on the given user or IP
- * 2) A rangeblock encompassing the given IP (smallest first)
- * 3) An autoblock on the given IP
+ * Load blocks from the database which target the specific target exactly, or which cover the
+ * vague target.
+ *
+ * @param User|String|null $specificTarget
+ * @param int|null $specificType
+ * @param bool $fromMaster
* @param User|string|null $vagueTarget Also search for blocks affecting this target. Doesn't
* make any sense to use TYPE_AUTO / TYPE_ID here. Leave blank to skip IP lookups.
* @throws MWException
- * @return bool Whether a relevant block was found
+ * @return DatabaseBlock[] Any relevant blocks
*/
- protected function newLoad( $vagueTarget = null ) {
- $db = wfGetDB( $this->mFromMaster ? DB_MASTER : DB_REPLICA );
-
- if ( $this->type !== null ) {
+ protected static function newLoad(
+ $specificTarget,
+ $specificType,
+ $fromMaster,
+ $vagueTarget = null
+ ) {
+ $db = wfGetDB( $fromMaster ? DB_MASTER : DB_REPLICA );
+
+ if ( $specificType !== null ) {
$conds = [
- 'ipb_address' => [ (string)$this->target ],
+ 'ipb_address' => [ (string)$specificTarget ],
];
} else {
$conds = [ 'ipb_address' => [] ];
$blockQuery['tables'], $blockQuery['fields'], $conds, __METHOD__, [], $blockQuery['joins']
);
- # This result could contain a block on the user, a block on the IP, and a russian-doll
- # set of rangeblocks. We want to choose the most specific one, so keep a leader board.
- $bestRow = null;
-
- # Lower will be better
- $bestBlockScore = 100;
-
+ $blocks = [];
+ $blockIds = [];
+ $autoBlocks = [];
foreach ( $res as $row ) {
$block = self::newFromRow( $row );
}
# Don't use anon only blocks on users
- if ( $this->type == self::TYPE_USER && !$block->isHardblock() ) {
+ if ( $specificType == self::TYPE_USER && !$block->isHardblock() ) {
continue;
}
+ // Check for duplicate autoblocks
+ if ( $block->getType() === self::TYPE_AUTO ) {
+ $autoBlocks[] = $block;
+ } else {
+ $blocks[] = $block;
+ $blockIds[] = $block->getId();
+ }
+ }
+
+ // Only add autoblocks that aren't duplicates
+ foreach ( $autoBlocks as $block ) {
+ if ( !in_array( $block->mParentBlockId, $blockIds ) ) {
+ $blocks[] = $block;
+ }
+ }
+
+ return $blocks;
+ }
+
+ /**
+ * Choose the most specific block from some combination of user, IP and IP range
+ * blocks. Decreasing order of specificity: user > IP > narrower IP range > wider IP
+ * range. A range that encompasses one IP address is ranked equally to a singe IP.
+ *
+ * Note that DatabaseBlock::chooseBlocks chooses blocks in a different way.
+ *
+ * This is refactored out from DatabaseBlock::newLoad.
+ *
+ * @param DatabaseBlock[] $blocks These should not include autoblocks or ID blocks
+ * @return DatabaseBlock|null The block with the most specific target
+ */
+ protected static function chooseMostSpecificBlock( $blocks ) {
+ if ( count( $blocks ) === 1 ) {
+ return $blocks[0];
+ }
+
+ # This result could contain a block on the user, a block on the IP, and a russian-doll
+ # set of rangeblocks. We want to choose the most specific one, so keep a leader board.
+ $bestBlock = null;
+
+ # Lower will be better
+ $bestBlockScore = 100;
+ foreach ( $blocks as $block ) {
if ( $block->getType() == self::TYPE_RANGE ) {
# This is the number of bits that are allowed to vary in the block, give
# or take some floating point errors
if ( $score < $bestBlockScore ) {
$bestBlockScore = $score;
- $bestRow = $row;
+ $bestBlock = $block;
}
}
- if ( $bestRow !== null ) {
- $this->initFromRow( $bestRow );
- return true;
- } else {
- return false;
- }
+ return $bestBlock;
}
/**
* not be the same as the target you gave if you used $vagueTarget!
*/
public static function newFromTarget( $specificTarget, $vagueTarget = null, $fromMaster = false ) {
+ $blocks = self::newListFromTarget( $specificTarget, $vagueTarget, $fromMaster );
+ return self::chooseMostSpecificBlock( $blocks );
+ }
+
+ /**
+ * This is similar to DatabaseBlock::newFromTarget, but it returns all the relevant blocks.
+ *
+ * @since 1.34
+ * @param string|User|int|null $specificTarget
+ * @param string|User|int|null $vagueTarget
+ * @param bool $fromMaster
+ * @return DatabaseBlock[] Any relevant blocks
+ */
+ public static function newListFromTarget(
+ $specificTarget,
+ $vagueTarget = null,
+ $fromMaster = false
+ ) {
list( $target, $type ) = self::parseTarget( $specificTarget );
if ( $type == self::TYPE_ID || $type == self::TYPE_AUTO ) {
- return self::newFromID( $target );
-
+ $block = self::newFromID( $target );
+ return $block ? [ $block ] : [];
} elseif ( $target === null && $vagueTarget == '' ) {
# We're not going to find anything useful here
# Be aware that the == '' check is explicit, since empty values will be
# passed by some callers (T31116)
- return null;
-
+ return [];
} elseif ( in_array(
$type,
[ self::TYPE_USER, self::TYPE_IP, self::TYPE_RANGE, null ] )
) {
- $block = new DatabaseBlock();
- $block->fromMaster( $fromMaster );
-
- if ( $type !== null ) {
- $block->setTarget( $target );
- }
-
- if ( $block->newLoad( $vagueTarget ) ) {
- return $block;
- }
+ return self::newLoad( $target, $type, $fromMaster, $vagueTarget );
}
- return null;
+ return [];
}
/**
* the same as the block's, to a maximum of 24 hours.
*
* @since 1.29
- *
+ * @deprecated since 1.34 Set a cookie via BlockManager::trackBlockWithCookie instead.
* @param WebResponse $response The response on which to set the cookie.
*/
public function setCookie( WebResponse $response ) {
- // Calculate the default expiry time.
- $maxExpiryTime = wfTimestamp( TS_MW, wfTimestamp() + ( 24 * 60 * 60 ) );
-
- // Use the block's expiry time only if it's less than the default.
- $expiryTime = $this->getExpiry();
- if ( $expiryTime === 'infinity' || $expiryTime > $maxExpiryTime ) {
- $expiryTime = $maxExpiryTime;
- }
-
- // Set the cookie. Reformat the MediaWiki datetime as a Unix timestamp for the cookie.
- $expiryValue = DateTime::createFromFormat( 'YmdHis', $expiryTime )->format( 'U' );
- $cookieOptions = [ 'httpOnly' => false ];
- $cookieValue = $this->getCookieValue();
- $response->setCookie( 'BlockID', $cookieValue, $expiryValue, $cookieOptions );
+ MediaWikiServices::getInstance()->getBlockManager()->setBlockCookie( $this, $response );
}
/**
* Unset the 'BlockID' cookie.
*
* @since 1.29
- *
+ * @deprecated since 1.34 Use BlockManager::clearBlockCookie instead
* @param WebResponse $response The response on which to unset the cookie.
*/
public static function clearCookie( WebResponse $response ) {
- $response->clearCookie( 'BlockID', [ 'httpOnly' => false ] );
+ MediaWikiServices::getInstance()->getBlockManager()->clearBlockCookie( $response );
}
/**
* be the block ID.
*
* @since 1.29
- *
+ * @deprecated since 1.34 Use BlockManager::trackBlockWithCookie instead of calling this
+ * directly
* @return string The block ID, probably concatenated with "!" and the HMAC.
*/
public function getCookieValue() {
- $config = RequestContext::getMain()->getConfig();
- $id = $this->getId();
- $secretKey = $config->get( 'SecretKey' );
- if ( !$secretKey ) {
- // If there's no secret key, don't append a HMAC.
- return $id;
- }
- $hmac = MWCryptHash::hmac( $id, $secretKey, false );
- $cookieValue = $id . '!' . $hmac;
- return $cookieValue;
+ return MediaWikiServices::getInstance()->getBlockManager()->getCookieValue( $this );
}
/**
* the ID and a HMAC (see DatabaseBlock::setCookie), but will sometimes only be the ID.
*
* @since 1.29
- *
+ * @deprecated since 1.34 Use BlockManager::getUserBlock instead
* @param string $cookieValue The string in which to find the ID.
- *
* @return int|null The block ID, or null if the HMAC is present and invalid.
*/
public static function getIdFromCookieValue( $cookieValue ) {
- // Extract the ID prefix from the cookie value (may be the whole value, if no bang found).
- $bangPos = strpos( $cookieValue, '!' );
- $id = ( $bangPos === false ) ? $cookieValue : substr( $cookieValue, 0, $bangPos );
- // Get the site-wide secret key.
- $config = RequestContext::getMain()->getConfig();
- $secretKey = $config->get( 'SecretKey' );
- if ( !$secretKey ) {
- // If there's no secret key, just use the ID as given.
- return $id;
- }
- $storedHmac = substr( $cookieValue, $bangPos + 1 );
- $calculatedHmac = MWCryptHash::hmac( $id, $secretKey, false );
- if ( $calculatedHmac === $storedHmac ) {
- return $id;
- } else {
- return null;
- }
+ return MediaWikiServices::getInstance()->getBlockManager()->getIdFromCookieValue( $cookieValue );
}
/**
}
/**
+ * @deprecated since 1.34 Use BlockManager::trackBlockWithCookie instead of calling this
+ * directly.
* @inheritDoc
*/
public function shouldTrackWithCookie( $isAnon ) {
+ wfDeprecated( __METHOD__, '1.34' );
$config = RequestContext::getMain()->getConfig();
switch ( $this->getType() ) {
case self::TYPE_IP: