MediaWiki master
ApiBlock.php
Go to the documentation of this file.
1<?php
23namespace MediaWiki\Api;
24
48use RuntimeException;
51
58class ApiBlock extends ApiBase {
59
61
62 private BlockPermissionCheckerFactory $blockPermissionCheckerFactory;
63 private BlockUserFactory $blockUserFactory;
64 private TitleFactory $titleFactory;
65 private UserIdentityLookup $userIdentityLookup;
66 private WatchedItemStoreInterface $watchedItemStore;
67 private BlockActionInfo $blockActionInfo;
68 private DatabaseBlockStore $blockStore;
69 private BlockTargetFactory $blockTargetFactory;
70
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 BlockTargetFactory $blockTargetFactory,
80 BlockActionInfo $blockActionInfo,
81 DatabaseBlockStore $blockStore,
82 WatchlistManager $watchlistManager,
83 UserOptionsLookup $userOptionsLookup
84 ) {
85 parent::__construct( $main, $action );
86
87 $this->blockPermissionCheckerFactory = $blockPermissionCheckerFactory;
88 $this->blockUserFactory = $blockUserFactory;
89 $this->titleFactory = $titleFactory;
90 $this->userIdentityLookup = $userIdentityLookup;
91 $this->watchedItemStore = $watchedItemStore;
92 $this->blockTargetFactory = $blockTargetFactory;
93 $this->blockActionInfo = $blockActionInfo;
94 $this->blockStore = $blockStore;
95
96 // Variables needed in ApiWatchlistTrait trait
97 $this->watchlistExpiryEnabled = $this->getConfig()->get( MainConfigNames::WatchlistExpiry );
98 $this->watchlistMaxDuration =
100 $this->watchlistManager = $watchlistManager;
101 $this->userOptionsLookup = $userOptionsLookup;
102 }
103
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' ] );
116
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' );
123 }
124 if ( $block->getType() === AbstractBlock::TYPE_AUTO ) {
125 $this->dieWithError( 'apierror-modify-autoblock' );
126 }
127 $status = $this->updateBlock( $block, $params );
128 } else {
129 if ( $params['user'] !== null ) {
130 $target = $this->blockTargetFactory->newFromUser( $params['user'] );
131 } else {
132 $targetUser = $this->userIdentityLookup->getUserIdentityByUserId( $params['userid'] );
133 if ( !$targetUser ) {
134 $this->dieWithError( [ 'apierror-nosuchuserid', $params['userid'] ], 'nosuchuserid' );
135 }
136 $target = $this->blockTargetFactory->newUserBlockTarget( $targetUser );
137 }
138 if ( $params['newblock'] ) {
139 $status = $this->insertBlock( $target, $params );
140 } else {
141 $blocks = $this->blockStore->newListFromTarget(
142 $target, null, false, DatabaseBlockStore::AUTO_NONE );
143 if ( count( $blocks ) === 0 ) {
144 $status = $this->insertBlock( $target, $params );
145 } elseif ( count( $blocks ) === 1 ) {
146 if ( $params['reblock'] ) {
147 $status = $this->updateBlock( $blocks[0], $params );
148 } else {
149 $status = Status::newFatal( 'ipb_already_blocked', $blocks[0]->getTargetName() );
150 }
151 } else {
152 $this->dieWithError( 'apierror-ambiguous-block', 'ambiguous-block' );
153 }
154 }
155 }
156
157 if ( !$status->isOK() ) {
158 $this->dieStatus( $status );
159 }
160
161 $block = $status->value;
162 if ( !( $block instanceof DatabaseBlock ) ) {
163 throw new RuntimeException( "Unexpected block class" );
164 }
165
166 $watchlistExpiry = $this->getExpiryFromParams( $params );
167 $userPage = Title::makeTitle( NS_USER, $block->getTargetName() );
168
169 if ( $params['watchuser'] && $block->getType() !== AbstractBlock::TYPE_RANGE ) {
170 $this->setWatch( 'watch', $userPage, $this->getUser(), null, $watchlistExpiry );
171 }
172
173 $res = [];
174
175 $res['user'] = $block->getTargetName();
176
177 $blockedUser = $block->getTargetUserIdentity();
178 $res['userID'] = $blockedUser ? $blockedUser->getId() : 0;
179
180 $res['expiry'] = ApiResult::formatExpiry( $block->getExpiry(), 'infinite' );
181 $res['id'] = $block->getId();
182
183 $res['reason'] = $params['reason'];
184 $res['anononly'] = $params['anononly'];
185 $res['nocreate'] = $params['nocreate'];
186 $res['autoblock'] = $params['autoblock'];
187 $res['noemail'] = $params['noemail'];
188 $res['hidename'] = $block->getHideName();
189 $res['allowusertalk'] = $params['allowusertalk'];
190 $res['watchuser'] = $params['watchuser'];
191 if ( $watchlistExpiry ) {
192 $expiry = $this->getWatchlistExpiry(
193 $this->watchedItemStore,
194 $userPage,
195 $this->getUser()
196 );
197 $res['watchlistexpiry'] = $expiry;
198 }
199 $res['partial'] = $params['partial'];
200 $res['pagerestrictions'] = $params['pagerestrictions'];
201 $res['namespacerestrictions'] = $params['namespacerestrictions'];
203 $res['actionrestrictions'] = $params['actionrestrictions'];
204 }
205
206 $this->getResult()->addValue( null, $this->getModuleName(), $res );
207 }
208
215 private function getBlockOptions( $params ) {
216 return [
217 'isCreateAccountBlocked' => $params['nocreate'],
218 'isEmailBlocked' => $params['noemail'],
219 'isHardBlock' => !$params['anononly'],
220 'isAutoblocking' => $params['autoblock'],
221 'isUserTalkEditBlocked' => !$params['allowusertalk'],
222 'isHideUser' => $params['hidename'],
223 'isPartial' => $params['partial'],
224 ];
225 }
226
232 private function getRestrictions( $params ) {
233 $restrictions = [];
234 if ( $params['partial'] ) {
235 $pageRestrictions = array_map(
236 [ PageRestriction::class, 'newFromTitle' ],
237 (array)$params['pagerestrictions']
238 );
239
240 $namespaceRestrictions = array_map( static function ( $id ) {
241 return new NamespaceRestriction( 0, $id );
242 }, (array)$params['namespacerestrictions'] );
243 $restrictions = array_merge( $pageRestrictions, $namespaceRestrictions );
244
246 $actionRestrictions = array_map( function ( $action ) {
247 return new ActionRestriction( 0, $this->blockActionInfo->getIdFromAction( $action ) );
248 }, (array)$params['actionrestrictions'] );
249 $restrictions = array_merge( $restrictions, $actionRestrictions );
250 }
251 }
252 return $restrictions;
253 }
254
260 private function checkEmailPermissions( $params ) {
261 if (
262 $params['noemail'] &&
263 !$this->blockPermissionCheckerFactory
264 ->newChecker( $this->getAuthority() )
265 ->checkEmailPermissions()
266 ) {
267 $this->dieWithError( 'apierror-cantblock-email' );
268 }
269 }
270
278 private function updateBlock( DatabaseBlock $block, $params ) {
279 $this->checkEmailPermissions( $params );
280 return $this->blockUserFactory->newUpdateBlock(
281 $block,
282 $this->getAuthority(),
283 $params['expiry'],
284 $params['reason'],
285 $this->getBlockOptions( $params ),
286 $this->getRestrictions( $params ),
287 $params['tags']
288 )->placeBlock();
289 }
290
298 private function insertBlock( $target, $params ) {
299 $this->checkEmailPermissions( $params );
300 return $this->blockUserFactory->newBlockUser(
301 $target,
302 $this->getAuthority(),
303 $params['expiry'],
304 $params['reason'],
305 $this->getBlockOptions( $params ),
306 $this->getRestrictions( $params ),
307 $params['tags']
308 )->placeBlock( $params['newblock'] ? BlockUser::CONFLICT_NEW : BlockUser::CONFLICT_FAIL );
309 }
310
311 public function mustBePosted() {
312 return true;
313 }
314
315 public function isWriteMode() {
316 return true;
317 }
318
319 public function getAllowedParams() {
320 $params = [
321 'id' => [ ParamValidator::PARAM_TYPE => 'integer' ],
322 'user' => [
323 ParamValidator::PARAM_TYPE => 'user',
324 UserDef::PARAM_ALLOWED_USER_TYPES => [ 'name', 'ip', 'temp', 'cidr', 'id' ],
325 UserDef::PARAM_RETURN_OBJECT => true,
326 ],
327 'userid' => [
328 ParamValidator::PARAM_TYPE => 'integer',
329 ParamValidator::PARAM_DEPRECATED => true,
330 ],
331 'expiry' => 'never',
332 'reason' => '',
333 'anononly' => false,
334 'nocreate' => false,
335 'autoblock' => false,
336 'noemail' => false,
337 'hidename' => false,
338 'allowusertalk' => false,
339 'reblock' => false,
340 'newblock' => false,
341 'watchuser' => false,
342 ];
343
344 // Params appear in the docs in the order they are defined,
345 // which is why this is here and not at the bottom.
346 if ( $this->watchlistExpiryEnabled ) {
347 $params += [
348 'watchlistexpiry' => [
349 ParamValidator::PARAM_TYPE => 'expiry',
350 ExpiryDef::PARAM_MAX => $this->watchlistMaxDuration,
351 ExpiryDef::PARAM_USE_MAX => true,
352 ]
353 ];
354 }
355
356 $pageLimit = $this->getConfig()->get( MainConfigNames::EnableMultiBlocks ) ? 50 : 10;
357
358 $params += [
359 'tags' => [
360 ParamValidator::PARAM_TYPE => 'tags',
361 ParamValidator::PARAM_ISMULTI => true,
362 ],
363 'partial' => false,
364 'pagerestrictions' => [
365 ParamValidator::PARAM_TYPE => 'title',
366 TitleDef::PARAM_MUST_EXIST => true,
367
368 // TODO: TitleDef returns instances of TitleValue when PARAM_RETURN_OBJECT is
369 // truthy. At the time of writing,
370 // MediaWiki\Block\Restriction\PageRestriction::newFromTitle accepts either
371 // string or instance of Title.
372 //TitleDef::PARAM_RETURN_OBJECT => true,
373
374 ParamValidator::PARAM_ISMULTI => true,
375 ParamValidator::PARAM_ISMULTI_LIMIT1 => $pageLimit,
376 ParamValidator::PARAM_ISMULTI_LIMIT2 => $pageLimit,
377 ],
378 'namespacerestrictions' => [
379 ParamValidator::PARAM_ISMULTI => true,
380 ParamValidator::PARAM_TYPE => 'namespace',
381 ],
382 ];
383
385 $params += [
386 'actionrestrictions' => [
387 ParamValidator::PARAM_ISMULTI => true,
388 ParamValidator::PARAM_TYPE => array_keys(
389 $this->blockActionInfo->getAllBlockActions()
390 ),
391 ],
392 ];
393 }
394
395 return $params;
396 }
397
398 public function needsToken() {
399 return 'csrf';
400 }
401
402 protected function getExamplesMessages() {
403 // phpcs:disable Generic.Files.LineLength
404 return [
405 'action=block&user=192.0.2.5&expiry=3%20days&reason=First%20strike&token=123ABC'
406 => 'apihelp-block-example-ip-simple',
407 'action=block&user=Vandal&expiry=never&reason=Vandalism&nocreate=&autoblock=&noemail=&token=123ABC'
408 => 'apihelp-block-example-user-complex',
409 ];
410 // phpcs:enable
411 }
412
413 public function getHelpUrls() {
414 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Block';
415 }
416}
417
419class_alias( ApiBlock::class, 'ApiBlock' );
const NS_USER
Definition Defines.php:67
This abstract class implements many basic API functions, and is the base of all API classes.
Definition ApiBase.php:75
dieWithError( $msg, $code=null, $data=null, $httpCode=0)
Abort execution with an error.
Definition ApiBase.php:1522
checkUserRightsAny( $rights)
Helper function for permission-denied errors.
Definition ApiBase.php:1631
getModuleName()
Get the name of the module being executed by this instance.
Definition ApiBase.php:557
requireNoConflictingParameters( $params, $trigger, $conflicts)
Die with an "invalid param mix" error if the parameters contain the trigger parameter and any of the ...
Definition ApiBase.php:1067
getResult()
Get the result object.
Definition ApiBase.php:696
dieStatus(StatusValue $status)
Throw an ApiUsageException based on the Status object.
Definition ApiBase.php:1573
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition ApiBase.php:837
requireOnlyOneParameter( $params,... $required)
Die if 0 or more than one of a certain set of parameters is set and not false.
Definition ApiBase.php:976
API module that facilitates the blocking of users.
Definition ApiBlock.php:58
execute()
Blocks the user specified in the parameters for the given expiry, with the given reason,...
Definition ApiBlock.php:110
getHelpUrls()
Return links to more detailed help pages about the module.
Definition ApiBlock.php:413
getAllowedParams()
Returns an array of allowed parameters (parameter name) => (default value) or (parameter name) => (ar...
Definition ApiBlock.php:319
getExamplesMessages()
Returns usage examples for this module.
Definition ApiBlock.php:402
mustBePosted()
Indicates whether this module must be called with a POST request.
Definition ApiBlock.php:311
__construct(ApiMain $main, string $action, BlockPermissionCheckerFactory $blockPermissionCheckerFactory, BlockUserFactory $blockUserFactory, TitleFactory $titleFactory, UserIdentityLookup $userIdentityLookup, WatchedItemStoreInterface $watchedItemStore, BlockTargetFactory $blockTargetFactory, BlockActionInfo $blockActionInfo, DatabaseBlockStore $blockStore, WatchlistManager $watchlistManager, UserOptionsLookup $userOptionsLookup)
Definition ApiBlock.php:71
isWriteMode()
Indicates whether this module requires write access to the wiki.
Definition ApiBlock.php:315
needsToken()
Returns the token type this module requires in order to execute.
Definition ApiBlock.php:398
This is the main API class, used for both external and internal processing.
Definition ApiMain.php:79
static formatExpiry( $expiry, $infinity='infinity')
Format an expiry timestamp for API output.
Defines the actions that can be blocked by a partial block.
Factory for BlockTarget objects.
Base class for block targets.
Handles the backend logic of blocking users.
Definition BlockUser.php:54
const CONFLICT_NEW
On conflict, create a new block.
Definition BlockUser.php:58
A DatabaseBlock (unlike a SystemBlock) is stored in the database, may give rise to autoblocks and may...
Restriction for partial blocks of actions.
A class containing constants representing the names of configuration variables.
const WatchlistExpiry
Name constant for the WatchlistExpiry setting, for use with Config::get()
const EnableMultiBlocks
Name constant for the EnableMultiBlocks setting, for use with Config::get()
const WatchlistExpiryMaxDuration
Name constant for the WatchlistExpiryMaxDuration setting, for use with Config::get()
const EnablePartialActionBlocks
Name constant for the EnablePartialActionBlocks setting, for use with Config::get()
Type definition for page titles.
Definition TitleDef.php:22
Type definition for user types.
Definition UserDef.php:27
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition Status.php:54
Creates Title objects.
Represents a title within MediaWiki.
Definition Title.php:78
Provides access to user options.
Service for formatting and validating API parameters.
Type definition for expiry timestamps.
Definition ExpiryDef.php:17
trait ApiWatchlistTrait
An ApiWatchlistTrait adds class properties and convenience methods for APIs that allow you to watch a...
Service for looking up UserIdentity.
getExpiryFromParams(array $params)
Get formatted expiry from the given parameters, or null if no expiry was provided.
setWatch(string $watch, PageIdentity $page, User $user, ?string $userOption=null, ?string $expiry=null)
Set a watch (or unwatch) based the based on a watchlist parameter.
getWatchlistExpiry(WatchedItemStoreInterface $store, PageIdentity $page, UserIdentity $user)
Get existing expiry from the database.