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\WatchlistManager;
32use Wikimedia\ParamValidator\ParamValidator;
33use Wikimedia\ParamValidator\TypeDef\ExpiryDef;
34
35/**
36 * API module that facilitates the unblocking of users. Requires API write mode
37 * to be enabled.
38 *
39 * @ingroup API
40 */
41class ApiUnblock extends ApiBase {
42
43    use ApiBlockInfoTrait;
44    use ApiWatchlistTrait;
45
46    private BlockPermissionCheckerFactory $permissionCheckerFactory;
47    private UnblockUserFactory $unblockUserFactory;
48    private UserIdentityLookup $userIdentityLookup;
49    private WatchedItemStoreInterface $watchedItemStore;
50
51    public function __construct(
52        ApiMain $main,
53        $action,
54        BlockPermissionCheckerFactory $permissionCheckerFactory,
55        UnblockUserFactory $unblockUserFactory,
56        UserIdentityLookup $userIdentityLookup,
57        WatchedItemStoreInterface $watchedItemStore,
58        WatchlistManager $watchlistManager,
59        UserOptionsLookup $userOptionsLookup
60    ) {
61        parent::__construct( $main, $action );
62
63        $this->permissionCheckerFactory = $permissionCheckerFactory;
64        $this->unblockUserFactory = $unblockUserFactory;
65        $this->userIdentityLookup = $userIdentityLookup;
66        $this->watchedItemStore = $watchedItemStore;
67
68        // Variables needed in ApiWatchlistTrait trait
69        $this->watchlistExpiryEnabled = $this->getConfig()->get( MainConfigNames::WatchlistExpiry );
70        $this->watchlistMaxDuration =
71            $this->getConfig()->get( MainConfigNames::WatchlistExpiryMaxDuration );
72        $this->watchlistManager = $watchlistManager;
73        $this->userOptionsLookup = $userOptionsLookup;
74    }
75
76    /**
77     * Unblocks the specified user or provides the reason the unblock failed.
78     */
79    public function execute() {
80        $performer = $this->getUser();
81        $params = $this->extractRequestParams();
82
83        $this->requireOnlyOneParameter( $params, 'id', 'user', 'userid' );
84
85        if ( !$this->getAuthority()->isAllowed( 'block' ) ) {
86            $this->dieWithError( 'apierror-permissiondenied-unblock', 'permissiondenied' );
87        }
88
89        if ( $params['userid'] !== null ) {
90            $identity = $this->userIdentityLookup->getUserIdentityByUserId( $params['userid'] );
91            if ( !$identity ) {
92                $this->dieWithError( [ 'apierror-nosuchuserid', $params['userid'] ], 'nosuchuserid' );
93            }
94            $params['user'] = $identity->getName();
95        }
96
97        $target = $params['id'] === null ? $params['user'] : "#{$params['id']}";
98
99        # T17810: blocked admins should have limited access here
100        $status = $this->permissionCheckerFactory
101            ->newBlockPermissionChecker(
102                $target,
103                $this->getAuthority()
104            )->checkBlockPermissions();
105        if ( $status !== true ) {
106            $this->dieWithError(
107                $status,
108                null,
109                // @phan-suppress-next-line PhanTypeMismatchArgumentNullable Block is checked and not null
110                [ 'blockinfo' => $this->getBlockDetails( $performer->getBlock() ) ]
111            );
112        }
113
114        $status = $this->unblockUserFactory->newUnblockUser(
115            $target,
116            $this->getAuthority(),
117            $params['reason'],
118            $params['tags'] ?? []
119        )->unblock();
120
121        if ( !$status->isOK() ) {
122            $this->dieStatus( $status );
123        }
124
125        $block = $status->getValue();
126        $targetType = $block->getType();
127        $targetName = $targetType === Block::TYPE_AUTO ? '' : $block->getTargetName();
128        $targetUserId = $block->getTargetUserIdentity() ? $block->getTargetUserIdentity()->getId() : 0;
129
130        $watchlistExpiry = $this->getExpiryFromParams( $params );
131        $watchuser = $params['watchuser'];
132        $userPage = Title::makeTitle( NS_USER, $targetName );
133        if ( $watchuser && $targetType !== Block::TYPE_RANGE && $targetType !== Block::TYPE_AUTO ) {
134            $this->setWatch( 'watch', $userPage, $this->getUser(), null, $watchlistExpiry );
135        } else {
136            $watchuser = false;
137            $watchlistExpiry = null;
138        }
139
140        $res = [
141            'id' => $block->getId(),
142            'user' => $targetName,
143            'userid' => $targetUserId,
144            'reason' => $params['reason'],
145            'watchuser' => $watchuser,
146        ];
147        if ( $watchlistExpiry !== null ) {
148            $res['watchlistexpiry'] = $this->getWatchlistExpiry(
149                $this->watchedItemStore,
150                $userPage,
151                $this->getUser()
152            );
153        }
154        $this->getResult()->addValue( null, $this->getModuleName(), $res );
155    }
156
157    public function mustBePosted() {
158        return true;
159    }
160
161    public function isWriteMode() {
162        return true;
163    }
164
165    public function getAllowedParams() {
166        $params = [
167            'id' => [
168                ParamValidator::PARAM_TYPE => 'integer',
169            ],
170            'user' => [
171                ParamValidator::PARAM_TYPE => 'user',
172                UserDef::PARAM_ALLOWED_USER_TYPES => [ 'name', 'ip', 'temp', 'cidr', 'id' ],
173            ],
174            'userid' => [
175                ParamValidator::PARAM_TYPE => 'integer',
176                ParamValidator::PARAM_DEPRECATED => true,
177            ],
178            'reason' => '',
179            'tags' => [
180                ParamValidator::PARAM_TYPE => 'tags',
181                ParamValidator::PARAM_ISMULTI => true,
182            ],
183            'watchuser' => false,
184        ];
185
186        // Params appear in the docs in the order they are defined,
187        // which is why this is here and not at the bottom.
188        // @todo Find better way to support insertion at arbitrary position
189        if ( $this->watchlistExpiryEnabled ) {
190            $params += [
191                'watchlistexpiry' => [
192                    ParamValidator::PARAM_TYPE => 'expiry',
193                    ExpiryDef::PARAM_MAX => $this->watchlistMaxDuration,
194                    ExpiryDef::PARAM_USE_MAX => true,
195                ]
196            ];
197        }
198
199        return $params;
200    }
201
202    public function needsToken() {
203        return 'csrf';
204    }
205
206    protected function getExamplesMessages() {
207        return [
208            'action=unblock&id=105'
209                => 'apihelp-unblock-example-id',
210            'action=unblock&user=Bob&reason=Sorry%20Bob'
211                => 'apihelp-unblock-example-user',
212        ];
213    }
214
215    public function getHelpUrls() {
216        return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Block';
217    }
218}