3 * Copyright © 2007 Roan Kattouw <roan.kattouw@gmail.com>
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.
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.
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
23 namespace MediaWiki\Api
;
25 use MediaWiki\Block\AbstractBlock
;
26 use MediaWiki\Block\BlockActionInfo
;
27 use MediaWiki\Block\BlockPermissionCheckerFactory
;
28 use MediaWiki\Block\BlockUser
;
29 use MediaWiki\Block\BlockUserFactory
;
30 use MediaWiki\Block\BlockUtils
;
31 use MediaWiki\Block\DatabaseBlock
;
32 use MediaWiki\Block\DatabaseBlockStore
;
33 use MediaWiki\Block\Restriction\ActionRestriction
;
34 use MediaWiki\Block\Restriction\NamespaceRestriction
;
35 use MediaWiki\Block\Restriction\PageRestriction
;
36 use MediaWiki\Block\Restriction\Restriction
;
37 use MediaWiki\MainConfigNames
;
38 use MediaWiki\ParamValidator\TypeDef\TitleDef
;
39 use MediaWiki\ParamValidator\TypeDef\UserDef
;
40 use MediaWiki\Status\Status
;
41 use MediaWiki\Title\Title
;
42 use MediaWiki\Title\TitleFactory
;
43 use MediaWiki\User\Options\UserOptionsLookup
;
44 use MediaWiki\User\UserIdentity
;
45 use MediaWiki\User\UserIdentityLookup
;
46 use MediaWiki\Watchlist\WatchedItemStoreInterface
;
47 use MediaWiki\Watchlist\WatchlistManager
;
49 use Wikimedia\ParamValidator\ParamValidator
;
50 use Wikimedia\ParamValidator\TypeDef\ExpiryDef
;
53 * API module that facilitates the blocking of users. Requires API write mode
58 class ApiBlock
extends ApiBase
{
60 use ApiWatchlistTrait
;
62 private BlockPermissionCheckerFactory
$blockPermissionCheckerFactory;
63 private BlockUserFactory
$blockUserFactory;
64 private TitleFactory
$titleFactory;
65 private UserIdentityLookup
$userIdentityLookup;
66 private WatchedItemStoreInterface
$watchedItemStore;
67 private BlockUtils
$blockUtils;
68 private BlockActionInfo
$blockActionInfo;
69 private DatabaseBlockStore
$blockStore;
71 public function __construct(
74 BlockPermissionCheckerFactory
$blockPermissionCheckerFactory,
75 BlockUserFactory
$blockUserFactory,
76 TitleFactory
$titleFactory,
77 UserIdentityLookup
$userIdentityLookup,
78 WatchedItemStoreInterface
$watchedItemStore,
79 BlockUtils
$blockUtils,
80 BlockActionInfo
$blockActionInfo,
81 DatabaseBlockStore
$blockStore,
82 WatchlistManager
$watchlistManager,
83 UserOptionsLookup
$userOptionsLookup
85 parent
::__construct( $main, $action );
87 $this->blockPermissionCheckerFactory
= $blockPermissionCheckerFactory;
88 $this->blockUserFactory
= $blockUserFactory;
89 $this->titleFactory
= $titleFactory;
90 $this->userIdentityLookup
= $userIdentityLookup;
91 $this->watchedItemStore
= $watchedItemStore;
92 $this->blockUtils
= $blockUtils;
93 $this->blockActionInfo
= $blockActionInfo;
94 $this->blockStore
= $blockStore;
96 // Variables needed in ApiWatchlistTrait trait
97 $this->watchlistExpiryEnabled
= $this->getConfig()->get( MainConfigNames
::WatchlistExpiry
);
98 $this->watchlistMaxDuration
=
99 $this->getConfig()->get( MainConfigNames
::WatchlistExpiryMaxDuration
);
100 $this->watchlistManager
= $watchlistManager;
101 $this->userOptionsLookup
= $userOptionsLookup;
105 * Blocks the user specified in the parameters for the given expiry, with the
106 * given reason, and with all other settings provided in the params. If the block
107 * succeeds, produces a result containing the details of the block and notice
108 * of success. If it fails, the result will specify the nature of the error.
110 public function execute() {
111 $this->checkUserRightsAny( 'block' );
112 $params = $this->extractRequestParams();
113 $this->requireOnlyOneParameter( $params, 'id', 'user', 'userid' );
114 $this->requireNoConflictingParameters( $params,
115 'id', [ 'newblock', 'reblock' ] );
117 if ( $params['id'] !== null ) {
118 $block = $this->blockStore
->newFromID( $params['id'], true );
121 [ 'apierror-nosuchblockid', $params['id'] ],
124 if ( $block->getType() === AbstractBlock
::TYPE_AUTO
) {
125 $this->dieWithError( 'apierror-modify-autoblock' );
127 $status = $this->updateBlock( $block, $params );
129 if ( $params['user'] !== null ) {
130 $target = $params['user'];
132 $target = $this->userIdentityLookup
->getUserIdentityByUserId( $params['userid'] );
134 $this->dieWithError( [ 'apierror-nosuchuserid', $params['userid'] ], 'nosuchuserid' );
137 if ( $params['newblock'] ) {
138 $status = $this->insertBlock( $target, $params );
140 $blocks = $this->blockStore
->newListFromTarget( $target );
141 if ( count( $blocks ) === 0 ) {
142 $status = $this->insertBlock( $target, $params );
143 } elseif ( count( $blocks ) === 1 ) {
144 if ( $params['reblock'] ) {
145 $status = $this->updateBlock( $blocks[0], $params );
147 $status = Status
::newFatal( 'ipb_already_blocked', $blocks[0]->getTargetName() );
150 $this->dieWithError( 'apierror-ambiguous-block', 'ambiguous-block' );
155 if ( !$status->isOK() ) {
156 $this->dieStatus( $status );
159 $block = $status->value
;
160 if ( !( $block instanceof DatabaseBlock
) ) {
161 throw new RuntimeException( "Unexpected block class" );
164 $watchlistExpiry = $this->getExpiryFromParams( $params );
165 $userPage = Title
::makeTitle( NS_USER
, $block->getTargetName() );
167 if ( $params['watchuser'] && $block->getType() !== AbstractBlock
::TYPE_RANGE
) {
168 $this->setWatch( 'watch', $userPage, $this->getUser(), null, $watchlistExpiry );
173 $res['user'] = $block->getTargetName();
175 $blockedUser = $block->getTargetUserIdentity();
176 $res['userID'] = $blockedUser ?
$blockedUser->getId() : 0;
178 $res['expiry'] = ApiResult
::formatExpiry( $block->getExpiry(), 'infinite' );
179 $res['id'] = $block->getId();
181 $res['reason'] = $params['reason'];
182 $res['anononly'] = $params['anononly'];
183 $res['nocreate'] = $params['nocreate'];
184 $res['autoblock'] = $params['autoblock'];
185 $res['noemail'] = $params['noemail'];
186 $res['hidename'] = $block->getHideName();
187 $res['allowusertalk'] = $params['allowusertalk'];
188 $res['watchuser'] = $params['watchuser'];
189 if ( $watchlistExpiry ) {
190 $expiry = $this->getWatchlistExpiry(
191 $this->watchedItemStore
,
195 $res['watchlistexpiry'] = $expiry;
197 $res['partial'] = $params['partial'];
198 $res['pagerestrictions'] = $params['pagerestrictions'];
199 $res['namespacerestrictions'] = $params['namespacerestrictions'];
200 if ( $this->getConfig()->get( MainConfigNames
::EnablePartialActionBlocks
) ) {
201 $res['actionrestrictions'] = $params['actionrestrictions'];
204 $this->getResult()->addValue( null, $this->getModuleName(), $res );
208 * Get the block options to be used for an insert or update
210 * @param array $params
213 private function getBlockOptions( $params ) {
215 'isCreateAccountBlocked' => $params['nocreate'],
216 'isEmailBlocked' => $params['noemail'],
217 'isHardBlock' => !$params['anononly'],
218 'isAutoblocking' => $params['autoblock'],
219 'isUserTalkEditBlocked' => !$params['allowusertalk'],
220 'isHideUser' => $params['hidename'],
221 'isPartial' => $params['partial'],
226 * Get the new block restrictions
227 * @param array $params
228 * @return Restriction[]
230 private function getRestrictions( $params ) {
232 if ( $params['partial'] ) {
233 $pageRestrictions = array_map(
234 [ PageRestriction
::class, 'newFromTitle' ],
235 (array)$params['pagerestrictions']
238 $namespaceRestrictions = array_map( static function ( $id ) {
239 return new NamespaceRestriction( 0, $id );
240 }, (array)$params['namespacerestrictions'] );
241 $restrictions = array_merge( $pageRestrictions, $namespaceRestrictions );
243 if ( $this->getConfig()->get( MainConfigNames
::EnablePartialActionBlocks
) ) {
244 $actionRestrictions = array_map( function ( $action ) {
245 return new ActionRestriction( 0, $this->blockActionInfo
->getIdFromAction( $action ) );
246 }, (array)$params['actionrestrictions'] );
247 $restrictions = array_merge( $restrictions, $actionRestrictions );
250 return $restrictions;
254 * Exit with an error if the user wants to block user-to-user email but is not allowed.
256 * @param array $params
258 private function checkEmailPermissions( $params ) {
260 $params['noemail'] &&
261 !$this->blockPermissionCheckerFactory
262 ->newBlockPermissionChecker( null, $this->getAuthority() )
263 ->checkEmailPermissions()
265 $this->dieWithError( 'apierror-cantblock-email' );
272 * @param DatabaseBlock $block
273 * @param array $params
276 private function updateBlock( DatabaseBlock
$block, $params ) {
277 $this->checkEmailPermissions( $params );
278 return $this->blockUserFactory
->newUpdateBlock(
280 $this->getAuthority(),
283 $this->getBlockOptions( $params ),
284 $this->getRestrictions( $params ),
292 * @param UserIdentity|string $target
293 * @param array $params
296 private function insertBlock( $target, $params ) {
297 $this->checkEmailPermissions( $params );
298 return $this->blockUserFactory
->newBlockUser(
300 $this->getAuthority(),
303 $this->getBlockOptions( $params ),
304 $this->getRestrictions( $params ),
306 )->placeBlock( $params['newblock'] ? BlockUser
::CONFLICT_NEW
: BlockUser
::CONFLICT_FAIL
);
309 public function mustBePosted() {
313 public function isWriteMode() {
317 public function getAllowedParams() {
319 'id' => [ ParamValidator
::PARAM_TYPE
=> 'integer' ],
321 ParamValidator
::PARAM_TYPE
=> 'user',
322 UserDef
::PARAM_ALLOWED_USER_TYPES
=> [ 'name', 'ip', 'temp', 'cidr', 'id' ],
325 ParamValidator
::PARAM_TYPE
=> 'integer',
326 ParamValidator
::PARAM_DEPRECATED
=> true,
332 'autoblock' => false,
335 'allowusertalk' => false,
338 'watchuser' => false,
341 // Params appear in the docs in the order they are defined,
342 // which is why this is here and not at the bottom.
343 if ( $this->watchlistExpiryEnabled
) {
345 'watchlistexpiry' => [
346 ParamValidator
::PARAM_TYPE
=> 'expiry',
347 ExpiryDef
::PARAM_MAX
=> $this->watchlistMaxDuration
,
348 ExpiryDef
::PARAM_USE_MAX
=> true,
355 ParamValidator
::PARAM_TYPE
=> 'tags',
356 ParamValidator
::PARAM_ISMULTI
=> true,
359 'pagerestrictions' => [
360 ParamValidator
::PARAM_TYPE
=> 'title',
361 TitleDef
::PARAM_MUST_EXIST
=> true,
363 // TODO: TitleDef returns instances of TitleValue when PARAM_RETURN_OBJECT is
364 // truthy. At the time of writing,
365 // MediaWiki\Block\Restriction\PageRestriction::newFromTitle accepts either
366 // string or instance of Title.
367 //TitleDef::PARAM_RETURN_OBJECT => true,
369 ParamValidator
::PARAM_ISMULTI
=> true,
370 ParamValidator
::PARAM_ISMULTI_LIMIT1
=> 10,
371 ParamValidator
::PARAM_ISMULTI_LIMIT2
=> 10,
373 'namespacerestrictions' => [
374 ParamValidator
::PARAM_ISMULTI
=> true,
375 ParamValidator
::PARAM_TYPE
=> 'namespace',
379 if ( $this->getConfig()->get( MainConfigNames
::EnablePartialActionBlocks
) ) {
381 'actionrestrictions' => [
382 ParamValidator
::PARAM_ISMULTI
=> true,
383 ParamValidator
::PARAM_TYPE
=> array_keys(
384 $this->blockActionInfo
->getAllBlockActions()
393 public function needsToken() {
397 protected function getExamplesMessages() {
398 // phpcs:disable Generic.Files.LineLength
400 'action=block&user=192.0.2.5&expiry=3%20days&reason=First%20strike&token=123ABC'
401 => 'apihelp-block-example-ip-simple',
402 'action=block&user=Vandal&expiry=never&reason=Vandalism&nocreate=&autoblock=&noemail=&token=123ABC'
403 => 'apihelp-block-example-user-complex',
408 public function getHelpUrls() {
409 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Block';
413 /** @deprecated class alias since 1.43 */
414 class_alias( ApiBlock
::class, 'ApiBlock' );