Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
81.55% covered (warning)
81.55%
84 / 103
50.00% covered (danger)
50.00%
4 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
ApiUnblock
81.55% covered (warning)
81.55%
84 / 103
50.00% covered (danger)
50.00%
4 / 8
23.77
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
1
 execute
90.91% covered (success)
90.91%
50 / 55
0.00% covered (danger)
0.00%
0 / 1
13.13
 mustBePosted
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isWriteMode
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getAllowedParams
75.00% covered (warning)
75.00%
21 / 28
0.00% covered (danger)
0.00%
0 / 1
2.06
 needsToken
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getExamplesMessages
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 getHelpUrls
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * Copyright © 2007 Roan Kattouw <roan.kattouw@gmail.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
19 *
20 * @file
21 */
22
23use MediaWiki\Block\Block;
24use MediaWiki\Block\BlockPermissionCheckerFactory;
25use MediaWiki\Block\UnblockUserFactory;
26use MediaWiki\MainConfigNames;
27use MediaWiki\ParamValidator\TypeDef\UserDef;
28use MediaWiki\Title\Title;
29use MediaWiki\User\Options\UserOptionsLookup;
30use MediaWiki\User\UserIdentityLookup;
31use MediaWiki\Watchlist\WatchedItemStoreInterface;
32use MediaWiki\Watchlist\WatchlistManager;
33use Wikimedia\ParamValidator\ParamValidator;
34use Wikimedia\ParamValidator\TypeDef\ExpiryDef;
35
36/**
37 * API module that facilitates the unblocking of users. Requires API write mode
38 * to be enabled.
39 *
40 * @ingroup API
41 */
42class ApiUnblock extends ApiBase {
43
44    use ApiBlockInfoTrait;
45    use ApiWatchlistTrait;
46
47    private BlockPermissionCheckerFactory $permissionCheckerFactory;
48    private UnblockUserFactory $unblockUserFactory;
49    private UserIdentityLookup $userIdentityLookup;
50    private WatchedItemStoreInterface $watchedItemStore;
51
52    public function __construct(
53        ApiMain $main,
54        $action,
55        BlockPermissionCheckerFactory $permissionCheckerFactory,
56        UnblockUserFactory $unblockUserFactory,
57        UserIdentityLookup $userIdentityLookup,
58        WatchedItemStoreInterface $watchedItemStore,
59        WatchlistManager $watchlistManager,
60        UserOptionsLookup $userOptionsLookup
61    ) {
62        parent::__construct( $main, $action );
63
64        $this->permissionCheckerFactory = $permissionCheckerFactory;
65        $this->unblockUserFactory = $unblockUserFactory;
66        $this->userIdentityLookup = $userIdentityLookup;
67        $this->watchedItemStore = $watchedItemStore;
68
69        // Variables needed in ApiWatchlistTrait trait
70        $this->watchlistExpiryEnabled = $this->getConfig()->get( MainConfigNames::WatchlistExpiry );
71        $this->watchlistMaxDuration =
72            $this->getConfig()->get( MainConfigNames::WatchlistExpiryMaxDuration );
73        $this->watchlistManager = $watchlistManager;
74        $this->userOptionsLookup = $userOptionsLookup;
75    }
76
77    /**
78     * Unblocks the specified user or provides the reason the unblock failed.
79     */
80    public function execute() {
81        $performer = $this->getUser();
82        $params = $this->extractRequestParams();
83
84        $this->requireOnlyOneParameter( $params, 'id', 'user', 'userid' );
85
86        if ( !$this->getAuthority()->isAllowed( 'block' ) ) {
87            $this->dieWithError( 'apierror-permissiondenied-unblock', 'permissiondenied' );
88        }
89
90        if ( $params['userid'] !== null ) {
91            $identity = $this->userIdentityLookup->getUserIdentityByUserId( $params['userid'] );
92            if ( !$identity ) {
93                $this->dieWithError( [ 'apierror-nosuchuserid', $params['userid'] ], 'nosuchuserid' );
94            }
95            $params['user'] = $identity->getName();
96        }
97
98        $target = $params['id'] === null ? $params['user'] : "#{$params['id']}";
99
100        # T17810: blocked admins should have limited access here
101        $status = $this->permissionCheckerFactory
102            ->newBlockPermissionChecker(
103                $target,
104                $this->getAuthority()
105            )->checkBlockPermissions();
106        if ( $status !== true ) {
107            $this->dieWithError(
108                $status,
109                null,
110                // @phan-suppress-next-line PhanTypeMismatchArgumentNullable Block is checked and not null
111                [ 'blockinfo' => $this->getBlockDetails( $performer->getBlock() ) ]
112            );
113        }
114
115        $status = $this->unblockUserFactory->newUnblockUser(
116            $target,
117            $this->getAuthority(),
118            $params['reason'],
119            $params['tags'] ?? []
120        )->unblock();
121
122        if ( !$status->isOK() ) {
123            $this->dieStatus( $status );
124        }
125
126        $block = $status->getValue();
127        $targetType = $block->getType();
128        $targetName = $targetType === Block::TYPE_AUTO ? '' : $block->getTargetName();
129        $targetUserId = $block->getTargetUserIdentity() ? $block->getTargetUserIdentity()->getId() : 0;
130
131        $watchlistExpiry = $this->getExpiryFromParams( $params );
132        $watchuser = $params['watchuser'];
133        $userPage = Title::makeTitle( NS_USER, $targetName );
134        if ( $watchuser && $targetType !== Block::TYPE_RANGE && $targetType !== Block::TYPE_AUTO ) {
135            $this->setWatch( 'watch', $userPage, $this->getUser(), null, $watchlistExpiry );
136        } else {
137            $watchuser = false;
138            $watchlistExpiry = null;
139        }
140
141        $res = [
142            'id' => $block->getId(),
143            'user' => $targetName,
144            'userid' => $targetUserId,
145            'reason' => $params['reason'],
146            'watchuser' => $watchuser,
147        ];
148        if ( $watchlistExpiry !== null ) {
149            $res['watchlistexpiry'] = $this->getWatchlistExpiry(
150                $this->watchedItemStore,
151                $userPage,
152                $this->getUser()
153            );
154        }
155        $this->getResult()->addValue( null, $this->getModuleName(), $res );
156    }
157
158    public function mustBePosted() {
159        return true;
160    }
161
162    public function isWriteMode() {
163        return true;
164    }
165
166    public function getAllowedParams() {
167        $params = [
168            'id' => [
169                ParamValidator::PARAM_TYPE => 'integer',
170            ],
171            'user' => [
172                ParamValidator::PARAM_TYPE => 'user',
173                UserDef::PARAM_ALLOWED_USER_TYPES => [ 'name', 'ip', 'temp', 'cidr', 'id' ],
174            ],
175            'userid' => [
176                ParamValidator::PARAM_TYPE => 'integer',
177                ParamValidator::PARAM_DEPRECATED => true,
178            ],
179            'reason' => '',
180            'tags' => [
181                ParamValidator::PARAM_TYPE => 'tags',
182                ParamValidator::PARAM_ISMULTI => true,
183            ],
184            'watchuser' => false,
185        ];
186
187        // Params appear in the docs in the order they are defined,
188        // which is why this is here and not at the bottom.
189        // @todo Find better way to support insertion at arbitrary position
190        if ( $this->watchlistExpiryEnabled ) {
191            $params += [
192                'watchlistexpiry' => [
193                    ParamValidator::PARAM_TYPE => 'expiry',
194                    ExpiryDef::PARAM_MAX => $this->watchlistMaxDuration,
195                    ExpiryDef::PARAM_USE_MAX => true,
196                ]
197            ];
198        }
199
200        return $params;
201    }
202
203    public function needsToken() {
204        return 'csrf';
205    }
206
207    protected function getExamplesMessages() {
208        return [
209            'action=unblock&id=105'
210                => 'apihelp-unblock-example-id',
211            'action=unblock&user=Bob&reason=Sorry%20Bob'
212                => 'apihelp-unblock-example-user',
213        ];
214    }
215
216    public function getHelpUrls() {
217        return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Block';
218    }
219}