Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
18.31% covered (danger)
18.31%
13 / 71
0.00% covered (danger)
0.00%
0 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
UnblockUser
18.31% covered (danger)
18.31%
13 / 71
0.00% covered (danger)
0.00%
0 / 4
261.41
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
12
 unblock
52.63% covered (warning)
52.63%
10 / 19
0.00% covered (danger)
0.00%
0 / 1
9.83
 unblockUnsafe
13.04% covered (danger)
13.04%
3 / 23
0.00% covered (danger)
0.00%
0 / 1
62.26
 log
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2
3/**
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with this program; if not, write to the Free Software Foundation, Inc.,
16 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 * http://www.gnu.org/copyleft/gpl.html
18 *
19 * @file
20 */
21
22namespace MediaWiki\Block;
23
24use ChangeTags;
25use ManualLogEntry;
26use MediaWiki\HookContainer\HookContainer;
27use MediaWiki\HookContainer\HookRunner;
28use MediaWiki\Permissions\Authority;
29use MediaWiki\Status\Status;
30use MediaWiki\Title\TitleValue;
31use MediaWiki\User\UserFactory;
32use MediaWiki\User\UserIdentity;
33use RevisionDeleteUser;
34
35/**
36 * Backend class for unblocking users
37 *
38 * @since 1.36
39 */
40class UnblockUser {
41    /** @var BlockPermissionChecker */
42    private $blockPermissionChecker;
43
44    /** @var DatabaseBlockStore */
45    private $blockStore;
46
47    /** @var BlockUtils */
48    private $blockUtils;
49
50    /** @var UserFactory */
51    private $userFactory;
52
53    /** @var HookRunner */
54    private $hookRunner;
55
56    /** @var UserIdentity|string */
57    private $target;
58
59    /** @var int */
60    private $targetType;
61
62    /** @var DatabaseBlock|null */
63    private $block;
64
65    /** @var Authority */
66    private $performer;
67
68    /** @var string */
69    private $reason;
70
71    /** @var array */
72    private $tags = [];
73
74    /**
75     * @param BlockPermissionCheckerFactory $blockPermissionCheckerFactory
76     * @param DatabaseBlockStore $blockStore
77     * @param BlockUtils $blockUtils
78     * @param UserFactory $userFactory
79     * @param HookContainer $hookContainer
80     * @param UserIdentity|string $target
81     * @param Authority $performer
82     * @param string $reason
83     * @param string[] $tags
84     */
85    public function __construct(
86        BlockPermissionCheckerFactory $blockPermissionCheckerFactory,
87        DatabaseBlockStore $blockStore,
88        BlockUtils $blockUtils,
89        UserFactory $userFactory,
90        HookContainer $hookContainer,
91        $target,
92        Authority $performer,
93        string $reason,
94        array $tags = []
95    ) {
96        // Process dependencies
97        $this->blockPermissionChecker = $blockPermissionCheckerFactory
98            ->newBlockPermissionChecker(
99                $target,
100                $performer
101            );
102        $this->blockStore = $blockStore;
103        $this->blockUtils = $blockUtils;
104        $this->userFactory = $userFactory;
105        $this->hookRunner = new HookRunner( $hookContainer );
106
107        // Process params
108        [ $this->target, $this->targetType ] = $this->blockUtils->parseBlockTarget( $target );
109        if (
110            $this->targetType === AbstractBlock::TYPE_AUTO &&
111            is_numeric( $this->target )
112        ) {
113            // Needed, because BlockUtils::parseBlockTarget will strip the # from autoblocks.
114            $this->target = '#' . $this->target;
115        }
116        $this->block = $this->blockStore->newFromTarget( $this->target );
117        $this->performer = $performer;
118        $this->reason = $reason;
119        $this->tags = $tags;
120    }
121
122    /**
123     * Unblock user
124     *
125     * @return Status
126     */
127    public function unblock(): Status {
128        $status = Status::newGood();
129
130        $basePermissionCheckResult = $this->blockPermissionChecker->checkBasePermissions(
131            $this->block instanceof DatabaseBlock && $this->block->getHideName()
132        );
133        if ( $basePermissionCheckResult !== true ) {
134            return $status->fatal( $basePermissionCheckResult );
135        }
136
137        $blockPermissionCheckResult = $this->blockPermissionChecker->checkBlockPermissions();
138        if ( $blockPermissionCheckResult !== true ) {
139            return $status->fatal( $blockPermissionCheckResult );
140        }
141
142        if ( count( $this->tags ) !== 0 ) {
143            $status->merge(
144                ChangeTags::canAddTagsAccompanyingChange(
145                    $this->tags,
146                    $this->performer
147                )
148            );
149        }
150
151        if ( !$status->isOK() ) {
152            return $status;
153        }
154        return $this->unblockUnsafe();
155    }
156
157    /**
158     * Unblock user without any sort of permission checks
159     *
160     * @internal This is public to be called in a maintenance script.
161     * @return Status
162     */
163    public function unblockUnsafe(): Status {
164        $status = Status::newGood();
165
166        if ( $this->block === null ) {
167            return $status->fatal( 'ipb_cant_unblock', $this->target );
168        }
169
170        if (
171            $this->block->getType() === AbstractBlock::TYPE_RANGE &&
172            $this->targetType === AbstractBlock::TYPE_IP
173        ) {
174            return $status->fatal( 'ipb_blocked_as_range', $this->target, $this->block->getTargetName() );
175        }
176
177        $denyReason = [ 'hookaborted' ];
178        $legacyUser = $this->userFactory->newFromAuthority( $this->performer );
179        if ( !$this->hookRunner->onUnblockUser( $this->block, $legacyUser, $denyReason ) ) {
180            foreach ( $denyReason as $key ) {
181                $status->fatal( $key );
182            }
183            return $status;
184        }
185
186        $deleteBlockStatus = $this->blockStore->deleteBlock( $this->block );
187        if ( !$deleteBlockStatus ) {
188            return $status->fatal( 'ipb_cant_unblock', $this->block->getTargetName() );
189        }
190
191        $this->hookRunner->onUnblockUserComplete( $this->block, $legacyUser );
192
193        // Unset _deleted fields as needed
194        $user = $this->block->getTargetUserIdentity();
195        if ( $this->block->getHideName() && $user ) {
196            $id = $user->getId();
197            RevisionDeleteUser::unsuppressUserName( $user->getName(), $id );
198        }
199
200        $this->log();
201
202        $status->setResult( $status->isOK(), $this->block );
203        return $status;
204    }
205
206    /**
207     * Log the unblock to Special:Log/block
208     */
209    private function log() {
210        // Redact IP for autoblocks
211        if ( $this->block->getType() === DatabaseBlock::TYPE_AUTO ) {
212            $page = TitleValue::tryNew( NS_USER, '#' . $this->block->getId() );
213        } else {
214            $page = TitleValue::tryNew( NS_USER, $this->block->getTargetName() );
215        }
216
217        $logEntry = new ManualLogEntry( 'block', 'unblock' );
218
219        if ( $page !== null ) {
220            $logEntry->setTarget( $page );
221        }
222        $logEntry->setComment( $this->reason );
223        $logEntry->setPerformer( $this->performer->getUser() );
224        $logEntry->addTags( $this->tags );
225        $logEntry->setRelations( [ 'ipb_id' => $this->block->getId() ] );
226        $logId = $logEntry->insert();
227        $logEntry->publish( $logId );
228    }
229
230}