Merge "mediawiki.special.changeslist is a skinStyle module"
[lhc/web/wiklou.git] / includes / Block.php
index 85fa341..060eebd 100644 (file)
@@ -57,10 +57,10 @@ class Block {
        private $mBlockEmail;
 
        /** @var bool */
-       private $mDisableUsertalk;
+       private $allowUsertalk;
 
        /** @var bool */
-       private $mCreateAccount;
+       private $blockCreateAccount;
 
        /** @var User|string */
        private $target;
@@ -68,7 +68,10 @@ class Block {
        /** @var int Hack for foreign blocking (CentralAuth) */
        private $forcedTargetID;
 
-       /** @var int Block::TYPE_ constant. Can only be USER, IP or RANGE internally */
+       /**
+        * @var int Block::TYPE_ constant. After the block has been loaded
+        * from the database, this can only be USER, IP or RANGE.
+        */
        private $type;
 
        /** @var User */
@@ -118,6 +121,9 @@ class Block {
         *                          created by MediaWiki rather than being stored
         *                          in the database. Value is a string to return
         *                          from self::getSystemBlockType().
+        *     sitewide bool        Disallow editing all pages and all contribution
+        *                          actions, except those specifically allowed by
+        *                          other block flags
         *
         * @since 1.26 accepts $options array instead of individual parameters; order
         * of parameters above reflects the original order
@@ -142,14 +148,6 @@ class Block {
                        'sitewide'        => true,
                ];
 
-               if ( func_num_args() > 1 || !is_array( $options ) ) {
-                       $options = array_combine(
-                               array_slice( array_keys( $defaults ), 0, func_num_args() ),
-                               func_get_args()
-                       );
-                       wfDeprecated( __METHOD__ . ' with multiple arguments', '1.26' );
-               }
-
                $options += $defaults;
 
                $this->setTarget( $options['address'] );
@@ -167,28 +165,26 @@ class Block {
                        $this->setBlocker( $options['byText'] );
                }
 
-               $this->mReason = $options['reason'];
-               $this->mTimestamp = wfTimestamp( TS_MW, $options['timestamp'] );
-               $this->mExpiry = wfGetDB( DB_REPLICA )->decodeExpiry( $options['expiry'] );
+               $this->setReason( $options['reason'] );
+               $this->setTimestamp( wfTimestamp( TS_MW, $options['timestamp'] ) );
+               $this->setExpiry( wfGetDB( DB_REPLICA )->decodeExpiry( $options['expiry'] ) );
 
                # Boolean settings
                $this->mAuto = (bool)$options['auto'];
-               $this->mHideName = (bool)$options['hideName'];
+               $this->setHideName( (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'] );
-               $this->prevents( 'editownusertalk', !$options['allowUsertalk'] );
-               $this->prevents( 'createaccount', (bool)$options['createAccount'] );
+               $this->isEmailBlocked( (bool)$options['blockEmail'] );
+               $this->isCreateAccountBlocked( (bool)$options['createAccount'] );
+               $this->isUsertalkEditAllowed( (bool)$options['allowUsertalk'] );
 
                $this->mFromMaster = false;
                $this->systemBlockType = $options['systemBlock'];
        }
 
        /**
-        * Load a blocked user from their block id.
+        * Load a block from the block id.
         *
         * @param int $id Block id to search for
         * @return Block|null
@@ -299,13 +295,13 @@ class Block {
                        && $this->type == $block->type
                        && $this->mAuto == $block->mAuto
                        && $this->isHardblock() == $block->isHardblock()
-                       && $this->prevents( 'createaccount' ) == $block->prevents( 'createaccount' )
-                       && $this->mExpiry == $block->mExpiry
+                       && $this->isCreateAccountBlocked() == $block->isCreateAccountBlocked()
+                       && $this->getExpiry() == $block->getExpiry()
                        && $this->isAutoblocking() == $block->isAutoblocking()
-                       && $this->mHideName == $block->mHideName
-                       && $this->prevents( 'sendemail' ) == $block->prevents( 'sendemail' )
-                       && $this->prevents( 'editownusertalk' ) == $block->prevents( 'editownusertalk' )
-                       && $this->mReason == $block->mReason
+                       && $this->getHideName() == $block->getHideName()
+                       && $this->isEmailBlocked() == $block->isEmailBlocked()
+                       && $this->isUsertalkEditAllowed() == $block->isUsertalkEditAllowed()
+                       && $this->getReason() == $block->getReason()
                        && $this->isSitewide() == $block->isSitewide()
                        // Block::getRestrictions() may perform a database query, so keep it at
                        // the end.
@@ -374,9 +370,6 @@ class Block {
                # Lower will be better
                $bestBlockScore = 100;
 
-               # This is begging for $this = $bestBlock, but that's not allowed in PHP :(
-               $bestBlockPreventsEdit = null;
-
                foreach ( $res as $row ) {
                        $block = self::newFromRow( $row );
 
@@ -408,13 +401,11 @@ class Block {
                        if ( $score < $bestBlockScore ) {
                                $bestBlockScore = $score;
                                $bestRow = $row;
-                               $bestBlockPreventsEdit = $block->prevents( 'edit' );
                        }
                }
 
                if ( $bestRow !== null ) {
                        $this->initFromRow( $bestRow );
-                       $this->prevents( 'edit', $bestBlockPreventsEdit );
                        return true;
                } else {
                        return false;
@@ -480,26 +471,28 @@ class Block {
                        $row->ipb_by, $row->ipb_by_text, $row->ipb_by_actor ?? null
                ) );
 
-               $this->mTimestamp = wfTimestamp( TS_MW, $row->ipb_timestamp );
+               $this->setTimestamp( wfTimestamp( TS_MW, $row->ipb_timestamp ) );
                $this->mAuto = $row->ipb_auto;
-               $this->mHideName = $row->ipb_deleted;
+               $this->setHideName( $row->ipb_deleted );
                $this->mId = (int)$row->ipb_id;
                $this->mParentBlockId = $row->ipb_parent_block_id;
 
                // I wish I didn't have to do this
                $db = wfGetDB( DB_REPLICA );
-               $this->mExpiry = $db->decodeExpiry( $row->ipb_expiry );
-               $this->mReason = CommentStore::getStore()
+               $this->setExpiry( $db->decodeExpiry( $row->ipb_expiry ) );
+               $this->setReason(
+                       CommentStore::getStore()
                        // Legacy because $row may have come from self::selectFields()
-                       ->getCommentLegacy( $db, 'ipb_reason', $row )->text;
+                       ->getCommentLegacy( $db, 'ipb_reason', $row )->text
+               );
 
                $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 );
-               $this->prevents( 'editownusertalk', !$row->ipb_allow_usertalk );
+               $this->isCreateAccountBlocked( $row->ipb_create_account );
+               $this->isEmailBlocked( $row->ipb_block_email );
+               $this->isUsertalkEditAllowed( $row->ipb_allow_usertalk );
        }
 
        /**
@@ -688,7 +681,7 @@ class Block {
         * @return array
         */
        protected function getDatabaseArray( IDatabase $dbw ) {
-               $expiry = $dbw->encodeExpiry( $this->mExpiry );
+               $expiry = $dbw->encodeExpiry( $this->getExpiry() );
 
                if ( $this->forcedTargetID ) {
                        $uid = $this->forcedTargetID;
@@ -699,20 +692,20 @@ class Block {
                $a = [
                        'ipb_address'          => (string)$this->target,
                        'ipb_user'             => $uid,
-                       'ipb_timestamp'        => $dbw->timestamp( $this->mTimestamp ),
+                       'ipb_timestamp'        => $dbw->timestamp( $this->getTimestamp() ),
                        'ipb_auto'             => $this->mAuto,
                        'ipb_anon_only'        => !$this->isHardblock(),
-                       'ipb_create_account'   => $this->prevents( 'createaccount' ),
+                       'ipb_create_account'   => $this->isCreateAccountBlocked(),
                        'ipb_enable_autoblock' => $this->isAutoblocking(),
                        'ipb_expiry'           => $expiry,
                        'ipb_range_start'      => $this->getRangeStart(),
                        'ipb_range_end'        => $this->getRangeEnd(),
-                       'ipb_deleted'          => intval( $this->mHideName ), // typecast required for SQLite
-                       'ipb_block_email'      => $this->prevents( 'sendemail' ),
-                       'ipb_allow_usertalk'   => !$this->prevents( 'editownusertalk' ),
+                       'ipb_deleted'          => intval( $this->getHideName() ), // typecast required for SQLite
+                       'ipb_block_email'      => $this->isEmailBlocked(),
+                       'ipb_allow_usertalk'   => $this->isUsertalkEditAllowed(),
                        'ipb_parent_block_id'  => $this->mParentBlockId,
                        'ipb_sitewide'         => $this->isSitewide(),
-               ] + CommentStore::getStore()->insert( $dbw, 'ipb_reason', $this->mReason )
+               ] + CommentStore::getStore()->insert( $dbw, 'ipb_reason', $this->getReason() )
                        + ActorMigration::newMigration()->getInsertValues( $dbw, 'ipb_by', $this->getBlocker() );
 
                return $a;
@@ -724,11 +717,11 @@ class Block {
         */
        protected function getAutoblockUpdateArray( IDatabase $dbw ) {
                return [
-                       'ipb_create_account'   => $this->prevents( 'createaccount' ),
-                       'ipb_deleted'          => (int)$this->mHideName, // typecast required for SQLite
-                       'ipb_allow_usertalk'   => !$this->prevents( 'editownusertalk' ),
+                       'ipb_create_account'   => $this->isCreateAccountBlocked(),
+                       'ipb_deleted'          => (int)$this->getHideName(), // typecast required for SQLite
+                       'ipb_allow_usertalk'   => $this->isUsertalkEditAllowed(),
                        'ipb_sitewide'         => $this->isSitewide(),
-               ] + CommentStore::getStore()->insert( $dbw, 'ipb_reason', $this->mReason )
+               ] + CommentStore::getStore()->insert( $dbw, 'ipb_reason', $this->getReason() )
                        + ActorMigration::newMigration()->getInsertValues( $dbw, 'ipb_by', $this->getBlocker() );
        }
 
@@ -892,7 +885,7 @@ class Block {
                        # Check if the block is an autoblock and would exceed the user block
                        # if renewed. If so, do nothing, otherwise prolong the block time...
                        if ( $ipblock->mAuto && // @todo Why not compare $ipblock->mExpiry?
-                               $this->mExpiry > self::getAutoblockExpiry( $ipblock->mTimestamp )
+                               $this->getExpiry() > self::getAutoblockExpiry( $ipblock->getTimestamp() )
                        ) {
                                # Reset block timestamp to now and its expiry to
                                # $wgAutoblockExpiry in the future
@@ -906,26 +899,28 @@ class Block {
                wfDebug( "Autoblocking {$this->getTarget()}@" . $autoblockIP . "\n" );
                $autoblock->setTarget( $autoblockIP );
                $autoblock->setBlocker( $this->getBlocker() );
-               $autoblock->mReason = wfMessage( 'autoblocker', $this->getTarget(), $this->mReason )
-                       ->inContentLanguage()->plain();
+               $autoblock->setReason(
+                       wfMessage( 'autoblocker', $this->getTarget(), $this->getReason() )
+                               ->inContentLanguage()->plain()
+               );
                $timestamp = wfTimestampNow();
-               $autoblock->mTimestamp = $timestamp;
+               $autoblock->setTimestamp( $timestamp );
                $autoblock->mAuto = 1;
-               $autoblock->prevents( 'createaccount', $this->prevents( 'createaccount' ) );
+               $autoblock->isCreateAccountBlocked( $this->isCreateAccountBlocked() );
                # Continue suppressing the name if needed
-               $autoblock->mHideName = $this->mHideName;
-               $autoblock->prevents( 'editownusertalk', $this->prevents( 'editownusertalk' ) );
+               $autoblock->setHideName( $this->getHideName() );
+               $autoblock->isUsertalkEditAllowed( $this->isUsertalkEditAllowed() );
                $autoblock->mParentBlockId = $this->mId;
                $autoblock->isSitewide( $this->isSitewide() );
                $autoblock->setRestrictions( $this->getRestrictions() );
 
-               if ( $this->mExpiry == 'infinity' ) {
+               if ( $this->getExpiry() == 'infinity' ) {
                        # Original block was indefinite, start an autoblock now
-                       $autoblock->mExpiry = self::getAutoblockExpiry( $timestamp );
+                       $autoblock->setExpiry( self::getAutoblockExpiry( $timestamp ) );
                } else {
                        # If the user is already blocked with an expiry date, we don't
                        # want to pile on top of that.
-                       $autoblock->mExpiry = min( $this->mExpiry, self::getAutoblockExpiry( $timestamp ) );
+                       $autoblock->setExpiry( min( $this->getExpiry(), self::getAutoblockExpiry( $timestamp ) ) );
                }
 
                # Insert the block...
@@ -960,10 +955,10 @@ class Block {
                $timestamp = wfTimestampNow();
                wfDebug( "Block::isExpired() checking current " . $timestamp . " vs $this->mExpiry\n" );
 
-               if ( !$this->mExpiry ) {
+               if ( !$this->getExpiry() ) {
                        return false;
                } else {
-                       return $timestamp > $this->mExpiry;
+                       return $timestamp > $this->getExpiry();
                }
        }
 
@@ -980,14 +975,14 @@ class Block {
         */
        public function updateTimestamp() {
                if ( $this->mAuto ) {
-                       $this->mTimestamp = wfTimestamp();
-                       $this->mExpiry = self::getAutoblockExpiry( $this->mTimestamp );
+                       $this->setTimestamp( wfTimestamp() );
+                       $this->setExpiry( self::getAutoblockExpiry( $this->getTimestamp() ) );
 
                        $dbw = wfGetDB( DB_MASTER );
                        $dbw->update( 'ipblocks',
                                [ /* SET */
-                                       'ipb_timestamp' => $dbw->timestamp( $this->mTimestamp ),
-                                       'ipb_expiry' => $dbw->timestamp( $this->mExpiry ),
+                                       'ipb_timestamp' => $dbw->timestamp( $this->getTimestamp() ),
+                                       'ipb_expiry' => $dbw->timestamp( $this->getExpiry() ),
                                ],
                                [ /* WHERE */
                                        'ipb_id' => $this->getId(),
@@ -1041,10 +1036,7 @@ class Block {
         * @return int (0 for foreign users)
         */
        public function getBy() {
-               $blocker = $this->getBlocker();
-               return ( $blocker instanceof User )
-                       ? $blocker->getId()
-                       : 0;
+               return $this->getBlocker()->getId();
        }
 
        /**
@@ -1053,10 +1045,7 @@ class Block {
         * @return string
         */
        public function getByName() {
-               $blocker = $this->getBlocker();
-               return ( $blocker instanceof User )
-                       ? $blocker->getName()
-                       : (string)$blocker; // username
+               return $this->getBlocker()->getName();
        }
 
        /**
@@ -1083,6 +1072,46 @@ class Block {
                return $this;
        }
 
+       /**
+        * Get the reason given for creating the block
+        *
+        * @since 1.33
+        * @return string
+        */
+       public function getReason() {
+               return $this->mReason;
+       }
+
+       /**
+        * Set the reason for creating the block
+        *
+        * @since 1.33
+        * @param string $reason
+        */
+       public function setReason( $reason ) {
+               $this->mReason = $reason;
+       }
+
+       /**
+        * Get whether the block hides the target's username
+        *
+        * @since 1.33
+        * @return bool The block hides the username
+        */
+       public function getHideName() {
+               return $this->mHideName;
+       }
+
+       /**
+        * Set whether ths block hides the target's username
+        *
+        * @since 1.33
+        * @param bool $hideName The block hides the username
+        */
+       public function setHideName( $hideName ) {
+               $this->mHideName = $hideName;
+       }
+
        /**
         * Get the system block type, if any
         * @since 1.29
@@ -1143,9 +1172,97 @@ class Block {
                return wfSetVar( $this->isSitewide, $x );
        }
 
+       /**
+        * Get or set the flag indicating whether this block blocks the target from
+        * creating an account. (Note that the flag may be overridden depending on
+        * global configs.)
+        *
+        * @since 1.33
+        * @param null|bool $x Value to set (if null, just get the property value)
+        * @return bool Value of the property
+        */
+       public function isCreateAccountBlocked( $x = null ) {
+               return wfSetVar( $this->blockCreateAccount, $x );
+       }
+
+       /**
+        * Get or set the flag indicating whether this block blocks the target from
+        * sending emails. (Note that the flag may be overridden depending on
+        * global configs.)
+        *
+        * @since 1.33
+        * @param null|bool $x Value to set (if null, just get the property value)
+        * @return bool Value of the property
+        */
+       public function isEmailBlocked( $x = null ) {
+               return wfSetVar( $this->mBlockEmail, $x );
+       }
+
+       /**
+        * Get or set the flag indicating whether this block blocks the target from
+        * editing their own user talk page. (Note that the flag may be overridden
+        * depending on global configs.)
+        *
+        * @since 1.33
+        * @param null|bool $x Value to set (if null, just get the property value)
+        * @return bool Value of the property
+        */
+       public function isUsertalkEditAllowed( $x = null ) {
+               return wfSetVar( $this->allowUsertalk, $x );
+       }
+
+       /**
+        * Determine whether the Block prevents a given right. A right
+        * may be blacklisted or whitelisted, or determined from a
+        * property on the Block object. For certain rights, the property
+        * may be overridden according to global configs.
+        *
+        * @since 1.33
+        * @param string $right Right to check
+        * @return bool|null null if unrecognized right or unset property
+        */
+       public function appliesToRight( $right ) {
+               $config = RequestContext::getMain()->getConfig();
+               $blockDisablesLogin = $config->get( 'BlockDisablesLogin' );
+
+               $res = null;
+               switch ( $right ) {
+                       case 'edit':
+                               // TODO: fix this case to return proper value
+                               $res = true;
+                               break;
+                       case 'createaccount':
+                               $res = $this->isCreateAccountBlocked();
+                               break;
+                       case 'sendemail':
+                               $res = $this->isEmailBlocked();
+                               break;
+                       case 'upload':
+                               // Until T6995 is completed
+                               $res = $this->isSitewide();
+                               break;
+                       case 'read':
+                               $res = false;
+                               break;
+                       case 'purge':
+                               $res = false;
+                               break;
+               }
+               if ( !$res && $blockDisablesLogin ) {
+                       // If a block would disable login, then it should
+                       // prevent any right that all users cannot do
+                       $anon = new User;
+                       $res = $anon->isAllowed( $right ) ? $res : true;
+               }
+
+               return $res;
+       }
+
        /**
         * Get/set whether the Block prevents a given action
         *
+        * @deprecated since 1.33, use appliesToRight to determine block
+        *  behaviour, and specific methods to get/set properties
         * @param string $action Action to check
         * @param bool|null $x Value for set, or null to just get value
         * @return bool|null Null for unrecognized rights.
@@ -1162,7 +1279,7 @@ class Block {
                                $res = true;
                                break;
                        case 'createaccount':
-                               $res = wfSetVar( $this->mCreateAccount, $x );
+                               $res = wfSetVar( $this->blockCreateAccount, $x );
                                break;
                        case 'sendemail':
                                $res = wfSetVar( $this->mBlockEmail, $x );
@@ -1176,7 +1293,8 @@ class Block {
                                // since partially blocked users are always allowed to edit
                                // their own talk page unless a restriction exists on the
                                // page or User_talk: namespace
-                               $res = wfSetVar( $this->mDisableUsertalk, $x );
+                               wfSetVar( $this->allowUsertalk, $x === null ? null : !$x );
+                               $res = !$this->isUserTalkEditAllowed();
 
                                // edit own user talk can be disabled by config
                                if ( !$blockAllowsUTEdit ) {
@@ -1206,10 +1324,10 @@ class Block {
         */
        public function getRedactedName() {
                if ( $this->mAuto ) {
-                       return Html::rawElement(
+                       return Html::element(
                                'span',
                                [ 'class' => 'mw-autoblockid' ],
-                               wfMessage( 'autoblockid', $this->mId )
+                               wfMessage( 'autoblockid', $this->mId )->text()
                        );
                } else {
                        return htmlspecialchars( $this->getTarget() );
@@ -1403,8 +1521,8 @@ class Block {
                // Sort hard blocks before soft ones and secondarily sort blocks
                // that disable account creation before those that don't.
                usort( $blocks, function ( Block $a, Block $b ) {
-                       $aWeight = (int)$a->isHardblock() . (int)$a->prevents( 'createaccount' );
-                       $bWeight = (int)$b->isHardblock() . (int)$b->prevents( 'createaccount' );
+                       $aWeight = (int)$a->isHardblock() . (int)$a->appliesToRight( 'createaccount' );
+                       $bWeight = (int)$b->isHardblock() . (int)$b->appliesToRight( 'createaccount' );
                        return strcmp( $bWeight, $aWeight ); // highest weight first
                } );
 
@@ -1428,7 +1546,7 @@ class Block {
                        // is why the order of the blocks matters
                        if ( !$block->isHardblock() && $blocksListExact['hard'] ) {
                                break;
-                       } elseif ( !$block->prevents( 'createaccount' ) && $blocksListExact['disable_create'] ) {
+                       } elseif ( !$block->appliesToRight( 'createaccount' ) && $blocksListExact['disable_create'] ) {
                                break;
                        }
 
@@ -1437,7 +1555,7 @@ class Block {
                                if ( (string)$block->getTarget() === $checkip ) {
                                        if ( $block->isHardblock() ) {
                                                $blocksListExact['hard'] = $blocksListExact['hard'] ?: $block;
-                                       } elseif ( $block->prevents( 'createaccount' ) ) {
+                                       } elseif ( $block->appliesToRight( 'createaccount' ) ) {
                                                $blocksListExact['disable_create'] = $blocksListExact['disable_create'] ?: $block;
                                        } elseif ( $block->mAuto ) {
                                                $blocksListExact['auto'] = $blocksListExact['auto'] ?: $block;
@@ -1452,7 +1570,7 @@ class Block {
                                ) {
                                        if ( $block->isHardblock() ) {
                                                $blocksListRange['hard'] = $blocksListRange['hard'] ?: $block;
-                                       } elseif ( $block->prevents( 'createaccount' ) ) {
+                                       } elseif ( $block->appliesToRight( 'createaccount' ) ) {
                                                $blocksListRange['disable_create'] = $blocksListRange['disable_create'] ?: $block;
                                        } elseif ( $block->mAuto ) {
                                                $blocksListRange['auto'] = $blocksListRange['auto'] ?: $block;
@@ -1547,7 +1665,9 @@ class Block {
        }
 
        /**
-        * Get the type of target for this particular block
+        * Get the type of target for this particular block. Autoblocks have whichever type
+        * corresponds to their target, so to detect if a block is an autoblock, we have to
+        * check the mAuto property instead.
         * @return int Block::TYPE_ constant, will never be TYPE_ID
         */
        public function getType() {
@@ -1578,14 +1698,45 @@ class Block {
        }
 
        /**
-        * @since 1.19
+        * Get the block expiry time
         *
-        * @return mixed|string
+        * @since 1.19
+        * @return string
         */
        public function getExpiry() {
                return $this->mExpiry;
        }
 
+       /**
+        * Set the block expiry time
+        *
+        * @since 1.33
+        * @param string $expiry
+        */
+       public function setExpiry( $expiry ) {
+               $this->mExpiry = $expiry;
+       }
+
+       /**
+        * Get the timestamp indicating when the block was created
+        *
+        * @since 1.33
+        * @return string
+        */
+       public function getTimestamp() {
+               return $this->mTimestamp;
+       }
+
+       /**
+        * Set the timestamp indicating when the block was created
+        *
+        * @since 1.33
+        * @param string $timestamp
+        */
+       public function setTimestamp( $timestamp ) {
+               $this->mTimestamp = $timestamp;
+       }
+
        /**
         * Set the target for this block, and update $this->type accordingly
         * @param mixed $target
@@ -1748,7 +1899,7 @@ class Block {
                        $link = $blocker;
                }
 
-               $reason = $this->mReason;
+               $reason = $this->getReason();
                if ( $reason == '' ) {
                        $reason = $context->msg( 'blockednoreason' )->text();
                }
@@ -1765,9 +1916,9 @@ class Block {
                        $context->getRequest()->getIP(),
                        $this->getByName(),
                        $systemBlockType ?? $this->getId(),
-                       $lang->formatExpiry( $this->mExpiry ),
+                       $lang->formatExpiry( $this->getExpiry() ),
                        (string)$intended,
-                       $lang->userTimeAndDate( $this->mTimestamp, $context->getUser() ),
+                       $lang->userTimeAndDate( $this->getTimestamp(), $context->getUser() ),
                ];
        }
 
@@ -1808,10 +1959,71 @@ class Block {
                return $this;
        }
 
+       /**
+        * Determine whether the block allows the user to edit their own
+        * user talk page. This is done separately from Block::appliesToRight
+        * because there is no right for editing one's own user talk page
+        * and because the user's talk page needs to be passed into the
+        * Block object, which is unaware of the user.
+        *
+        * The ipb_allow_usertalk flag (which corresponds to the property
+        * allowUsertalk) is used on sitewide blocks and partial blocks
+        * that contain a namespace restriction on the user talk namespace,
+        * but do not contain a page restriction on the user's talk page.
+        * For all other (i.e. most) partial blocks, the flag is ignored,
+        * and the user can always edit their user talk page unless there
+        * is a page restriction on their user talk page, in which case
+        * they can never edit it. (Ideally the flag would be stored as
+        * null in these cases, but the database field isn't nullable.)
+        *
+        * This method does not validate that the passed in talk page belongs to the
+        * block target since the target (an IP) might not be the same as the user's
+        * talk page (if they are logged in).
+        *
+        * @since 1.33
+        * @param Title|null $usertalk The user's user talk page. If null,
+        *  and if the target is a User, the target's userpage is used
+        * @return bool The user can edit their talk page
+        */
+       public function appliesToUsertalk( Title $usertalk = null ) {
+               if ( !$usertalk ) {
+                       if ( $this->target instanceof User ) {
+                               $usertalk = $this->target->getTalkPage();
+                       } else {
+                               throw new InvalidArgumentException(
+                                       '$usertalk must be provided if block target is not a user/IP'
+                               );
+                       }
+               }
+
+               if ( $usertalk->getNamespace() !== NS_USER_TALK ) {
+                       throw new InvalidArgumentException(
+                               '$usertalk must be a user talk page'
+                       );
+               }
+
+               if ( !$this->isSitewide() ) {
+                       if ( $this->appliesToPage( $usertalk->getArticleID() ) ) {
+                               return true;
+                       }
+                       if ( !$this->appliesToNamespace( NS_USER_TALK ) ) {
+                               return false;
+                       }
+               }
+
+               // This is a type of block which uses the ipb_allow_usertalk
+               // flag. The flag can still be overridden by global configs.
+               $config = RequestContext::getMain()->getConfig();
+               if ( !$config->get( 'BlockAllowsUTEdit' ) ) {
+                       return true;
+               }
+               return !$this->isUsertalkEditAllowed();
+       }
+
        /**
         * Checks if a block applies to a particular title
         *
-        * This check does not consider whether `$this->prevents( 'editownusertalk' )`
+        * This check does not consider whether `$this->isUsertalkEditAllowed`
         * returns false, as the identity of the user making the hypothetical edit
         * isn't known here (particularly in the case of IP hardblocks, range
         * blocks, and auto-blocks).
@@ -1860,7 +2072,7 @@ class Block {
        /**
         * Checks if a block applies to a particular page
         *
-        * This check does not consider whether `$this->prevents( 'editownusertalk' )`
+        * This check does not consider whether `$this->isUsertalkEditAllowed`
         * returns false, as the identity of the user making the hypothetical edit
         * isn't known here (particularly in the case of IP hardblocks, range
         * blocks, and auto-blocks).