Localisation updates from https://translatewiki.net.
[mediawiki.git] / includes / api / ApiBlock.php
blob7df97a950c649bc36b2d40162028dad074b2dd39
1 <?php
2 /**
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
20 * @file
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;
48 use RuntimeException;
49 use Wikimedia\ParamValidator\ParamValidator;
50 use Wikimedia\ParamValidator\TypeDef\ExpiryDef;
52 /**
53 * API module that facilitates the blocking of users. Requires API write mode
54 * to be enabled.
56 * @ingroup API
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(
72 ApiMain $main,
73 string $action,
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
84 ) {
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 );
119 if ( !$block ) {
120 $this->dieWithError(
121 [ 'apierror-nosuchblockid', $params['id'] ],
122 'nosuchblockid' );
124 if ( $block->getType() === AbstractBlock::TYPE_AUTO ) {
125 $this->dieWithError( 'apierror-modify-autoblock' );
127 $status = $this->updateBlock( $block, $params );
128 } else {
129 if ( $params['user'] !== null ) {
130 $target = $params['user'];
131 } else {
132 $target = $this->userIdentityLookup->getUserIdentityByUserId( $params['userid'] );
133 if ( !$target ) {
134 $this->dieWithError( [ 'apierror-nosuchuserid', $params['userid'] ], 'nosuchuserid' );
137 if ( $params['newblock'] ) {
138 $status = $this->insertBlock( $target, $params );
139 } else {
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 );
146 } else {
147 $status = Status::newFatal( 'ipb_already_blocked', $blocks[0]->getTargetName() );
149 } else {
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 );
171 $res = [];
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,
192 $userPage,
193 $this->getUser()
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
211 * @return array
213 private function getBlockOptions( $params ) {
214 return [
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 ) {
231 $restrictions = [];
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 ) {
259 if (
260 $params['noemail'] &&
261 !$this->blockPermissionCheckerFactory
262 ->newBlockPermissionChecker( null, $this->getAuthority() )
263 ->checkEmailPermissions()
265 $this->dieWithError( 'apierror-cantblock-email' );
270 * Update a block
272 * @param DatabaseBlock $block
273 * @param array $params
274 * @return Status
276 private function updateBlock( DatabaseBlock $block, $params ) {
277 $this->checkEmailPermissions( $params );
278 return $this->blockUserFactory->newUpdateBlock(
279 $block,
280 $this->getAuthority(),
281 $params['expiry'],
282 $params['reason'],
283 $this->getBlockOptions( $params ),
284 $this->getRestrictions( $params ),
285 $params['tags']
286 )->placeBlock();
290 * Insert a block
292 * @param UserIdentity|string $target
293 * @param array $params
294 * @return Status
296 private function insertBlock( $target, $params ) {
297 $this->checkEmailPermissions( $params );
298 return $this->blockUserFactory->newBlockUser(
299 $target,
300 $this->getAuthority(),
301 $params['expiry'],
302 $params['reason'],
303 $this->getBlockOptions( $params ),
304 $this->getRestrictions( $params ),
305 $params['tags']
306 )->placeBlock( $params['newblock'] ? BlockUser::CONFLICT_NEW : BlockUser::CONFLICT_FAIL );
309 public function mustBePosted() {
310 return true;
313 public function isWriteMode() {
314 return true;
317 public function getAllowedParams() {
318 $params = [
319 'id' => [ ParamValidator::PARAM_TYPE => 'integer' ],
320 'user' => [
321 ParamValidator::PARAM_TYPE => 'user',
322 UserDef::PARAM_ALLOWED_USER_TYPES => [ 'name', 'ip', 'temp', 'cidr', 'id' ],
324 'userid' => [
325 ParamValidator::PARAM_TYPE => 'integer',
326 ParamValidator::PARAM_DEPRECATED => true,
328 'expiry' => 'never',
329 'reason' => '',
330 'anononly' => false,
331 'nocreate' => false,
332 'autoblock' => false,
333 'noemail' => false,
334 'hidename' => false,
335 'allowusertalk' => false,
336 'reblock' => false,
337 'newblock' => 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 ) {
344 $params += [
345 'watchlistexpiry' => [
346 ParamValidator::PARAM_TYPE => 'expiry',
347 ExpiryDef::PARAM_MAX => $this->watchlistMaxDuration,
348 ExpiryDef::PARAM_USE_MAX => true,
353 $params += [
354 'tags' => [
355 ParamValidator::PARAM_TYPE => 'tags',
356 ParamValidator::PARAM_ISMULTI => true,
358 'partial' => false,
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 ) ) {
380 $params += [
381 'actionrestrictions' => [
382 ParamValidator::PARAM_ISMULTI => true,
383 ParamValidator::PARAM_TYPE => array_keys(
384 $this->blockActionInfo->getAllBlockActions()
390 return $params;
393 public function needsToken() {
394 return 'csrf';
397 protected function getExamplesMessages() {
398 // phpcs:disable Generic.Files.LineLength
399 return [
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',
405 // phpcs:enable
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' );