Merge "Hard deprecate Preprocessor_DOM"
[lhc/web/wiklou.git] / includes / block / DatabaseBlock.php
index 6d9dd13..ba08d54 100644 (file)
@@ -26,7 +26,6 @@ use ActorMigration;
 use AutoCommitUpdate;
 use BadMethodCallException;
 use CommentStore;
-use DateTime;
 use DeferredUpdates;
 use Hooks;
 use Html;
@@ -36,7 +35,6 @@ use MediaWiki\Block\Restriction\NamespaceRestriction;
 use MediaWiki\Block\Restriction\PageRestriction;
 use MediaWiki\Block\Restriction\Restriction;
 use MediaWiki\MediaWikiServices;
-use MWCryptHash;
 use MWException;
 use RequestContext;
 use stdClass;
@@ -264,21 +262,28 @@ class DatabaseBlock extends AbstractBlock {
        }
 
        /**
-        * 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' => [] ];
@@ -317,13 +322,9 @@ class DatabaseBlock extends AbstractBlock {
                        $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 );
 
@@ -333,10 +334,53 @@ class DatabaseBlock extends AbstractBlock {
                        }
 
                        # 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
@@ -354,16 +398,11 @@ class DatabaseBlock extends AbstractBlock {
 
                        if ( $score < $bestBlockScore ) {
                                $bestBlockScore = $score;
-                               $bestRow = $row;
+                               $bestBlock = $block;
                        }
                }
 
-               if ( $bestRow !== null ) {
-                       $this->initFromRow( $bestRow );
-                       return true;
-               } else {
-                       return false;
-               }
+               return $bestBlock;
        }
 
        /**
@@ -1120,32 +1159,40 @@ class DatabaseBlock extends AbstractBlock {
         *     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 [];
        }
 
        /**
@@ -1348,35 +1395,22 @@ class DatabaseBlock extends AbstractBlock {
         * 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 );
        }
 
        /**
@@ -1385,20 +1419,12 @@ class DatabaseBlock extends AbstractBlock {
         * 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 );
        }
 
        /**
@@ -1406,29 +1432,12 @@ class DatabaseBlock extends AbstractBlock {
         * 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 );
        }
 
        /**
@@ -1565,9 +1574,12 @@ class DatabaseBlock extends AbstractBlock {
        }
 
        /**
+        * @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: