MediaWiki master
ApiBlock.php
Go to the documentation of this file.
1<?php
9namespace MediaWiki\Api;
10
33use RuntimeException;
36
43class ApiBlock extends ApiBase {
44
46
47 public function __construct(
48 ApiMain $main,
49 string $action,
50 private readonly BlockPermissionCheckerFactory $blockPermissionCheckerFactory,
51 private readonly BlockUserFactory $blockUserFactory,
52 private readonly UserIdentityLookup $userIdentityLookup,
53 WatchedItemStoreInterface $watchedItemStore,
54 private readonly BlockTargetFactory $blockTargetFactory,
55 private readonly BlockActionInfo $blockActionInfo,
56 private readonly DatabaseBlockStore $blockStore,
57 WatchlistManager $watchlistManager,
58 UserOptionsLookup $userOptionsLookup,
59 ) {
60 parent::__construct( $main, $action );
61
62 $this->watchedItemStore = $watchedItemStore;
63
64 // Variables needed in ApiWatchlistTrait trait
65 $this->watchlistExpiryEnabled = $this->getConfig()->get( MainConfigNames::WatchlistExpiry );
66 $this->watchlistMaxDuration =
68 $this->watchlistManager = $watchlistManager;
69 $this->userOptionsLookup = $userOptionsLookup;
70 }
71
78 public function execute() {
79 $this->checkUserRightsAny( 'block' );
80 $params = $this->extractRequestParams();
81 $this->requireOnlyOneParameter( $params, 'id', 'user', 'userid' );
82 $this->requireMaxOneParameter( $params, 'newblock', 'reblock' );
83 $this->requireNoConflictingParameters( $params,
84 'id', [ 'newblock', 'reblock' ] );
85
86 $additionalBlocksStatuses = [];
87 if ( $params['id'] !== null ) {
88 $block = $this->blockStore->newFromID( $params['id'], true );
89 if ( !$block ) {
90 $this->dieWithError(
91 [ 'apierror-nosuchblockid', $params['id'] ],
92 'nosuchblockid' );
93 }
94 if ( $block->getType() === AbstractBlock::TYPE_AUTO ) {
95 $this->dieWithError( 'apierror-modify-autoblock' );
96 }
97 $status = $this->updateBlock( $block, $params );
98 } else {
99 if ( $params['user'] !== null ) {
100 $target = $this->blockTargetFactory->newFromUser( $params['user'] );
101 } else {
102 $targetUser = $this->userIdentityLookup->getUserIdentityByUserId( $params['userid'] );
103 if ( !$targetUser ) {
104 $this->dieWithError( [ 'apierror-nosuchuserid', $params['userid'] ], 'nosuchuserid' );
105 }
106 $target = $this->blockTargetFactory->newUserBlockTarget( $targetUser );
107 }
108 if ( $params['newblock'] ) {
109 $status = $this->insertBlock( $target, $params );
110
111 // Don't allow post-processing if the base block fails
112 if ( !$status->isOK() ) {
113 $this->dieStatus( $status );
114 }
115
116 // Only support additional blocks if multiblocks are enabled
117 if ( $this->getConfig()->get( MainConfigNames::EnableMultiBlocks ) ) {
118 $targetUserIdentity = $this->userIdentityLookup->getUserIdentityByName( $target->toString() );
119 if ( $targetUserIdentity ) {
120 $this->getHookRunner()->onApiBlockSucceeded(
121 $this,
122 $this->getAuthority(),
123 $targetUserIdentity,
124 $params,
125 $additionalBlocksStatuses
126 );
127 }
128 }
129 } else {
130 $blocks = $this->blockStore->newListFromTarget(
131 $target, null, false, DatabaseBlockStore::AUTO_NONE );
132 if ( count( $blocks ) === 0 ) {
133 $status = $this->insertBlock( $target, $params );
134 } elseif ( count( $blocks ) === 1 ) {
135 if ( $params['reblock'] ) {
136 $status = $this->updateBlock( $blocks[0], $params );
137 } else {
138 $status = Status::newFatal( 'ipb_already_blocked', $blocks[0]->getTargetName() );
139 }
140 } else {
141 $this->dieWithError( 'apierror-ambiguous-block', 'ambiguous-block' );
142 }
143 }
144 }
145
146 if ( !$status->isOK() ) {
147 $this->dieStatus( $status );
148 }
149
150 $block = $status->value;
151 if ( !( $block instanceof DatabaseBlock ) ) {
152 throw new RuntimeException( "Unexpected block class" );
153 }
154
155 $userPage = Title::makeTitle( NS_USER, $block->getTargetName() );
156 $watchlistExpiry = $this->getExpiryFromParams( $params, $userPage, $this->getUser() );
157
158 if ( $params['watchuser'] && $block->getType() !== AbstractBlock::TYPE_RANGE ) {
159 $this->setWatch( 'watch', $userPage, $this->getUser(), null, $watchlistExpiry );
160 }
161
162 $res = [];
163
164 $res['user'] = $block->getTargetName();
165
166 $blockedUser = $block->getTargetUserIdentity();
167 $res['userID'] = $blockedUser ? $blockedUser->getId() : 0;
168
169 $res['expiry'] = ApiResult::formatExpiry( $block->getExpiry(), 'infinite' );
170 $res['id'] = $block->getId();
171
172 $res['reason'] = $params['reason'];
173 $res['anononly'] = $params['anononly'];
174 $res['nocreate'] = $params['nocreate'];
175 $res['autoblock'] = $params['autoblock'];
176 $res['noemail'] = $params['noemail'];
177 $res['hidename'] = $block->getHideName();
178 $res['allowusertalk'] = $params['allowusertalk'];
179 $res['watchuser'] = $params['watchuser'];
180 if ( $watchlistExpiry ) {
181 $expiry = $this->getWatchlistExpiry(
182 $this->watchedItemStore,
183 $userPage,
184 $this->getUser()
185 );
186 $res['watchlistexpiry'] = $expiry;
187 }
188 $res['partial'] = $params['partial'];
189 $res['pagerestrictions'] = $params['pagerestrictions'];
190 $res['namespacerestrictions'] = $params['namespacerestrictions'];
191 $res['actionrestrictions'] = $params['actionrestrictions'];
192 $res['additionalBlocksStatuses'] = $additionalBlocksStatuses;
193
194 $this->getResult()->addValue( null, $this->getModuleName(), $res );
195 }
196
203 private function getBlockOptions( $params ) {
204 return [
205 'isCreateAccountBlocked' => $params['nocreate'],
206 'isEmailBlocked' => $params['noemail'],
207 'isHardBlock' => !$params['anononly'],
208 'isAutoblocking' => $params['autoblock'],
209 'isUserTalkEditBlocked' => !$params['allowusertalk'],
210 'isHideUser' => $params['hidename'],
211 'isPartial' => $params['partial'],
212 ];
213 }
214
220 private function getRestrictions( $params ) {
221 $restrictions = [];
222 if ( $params['partial'] ) {
223 $pageRestrictions = array_map(
224 PageRestriction::newFromTitle( ... ),
225 (array)$params['pagerestrictions']
226 );
227
228 $namespaceRestrictions = array_map( static function ( $id ) {
229 return new NamespaceRestriction( 0, $id );
230 }, (array)$params['namespacerestrictions'] );
231 $restrictions = array_merge( $pageRestrictions, $namespaceRestrictions );
232
233 $actionRestrictions = array_map( function ( $action ) {
234 return new ActionRestriction( 0, $this->blockActionInfo->getIdFromAction( $action ) );
235 }, (array)$params['actionrestrictions'] );
236 $restrictions = array_merge( $restrictions, $actionRestrictions );
237 }
238 return $restrictions;
239 }
240
246 private function checkEmailPermissions( $params ) {
247 if (
248 $params['noemail'] &&
249 !$this->blockPermissionCheckerFactory
250 ->newChecker( $this->getAuthority() )
251 ->checkEmailPermissions()
252 ) {
253 $this->dieWithError( 'apierror-cantblock-email' );
254 }
255 }
256
264 private function updateBlock( DatabaseBlock $block, $params ) {
265 $this->checkEmailPermissions( $params );
266 return $this->blockUserFactory->newUpdateBlock(
267 $block,
268 $this->getAuthority(),
269 $params['expiry'],
270 $params['reason'],
271 $this->getBlockOptions( $params ),
272 $this->getRestrictions( $params ),
273 $params['tags']
274 )->placeBlock();
275 }
276
284 public function insertBlock( $target, $params ) {
285 $this->checkEmailPermissions( $params );
286 return $this->blockUserFactory->newBlockUser(
287 $target,
288 $this->getAuthority(),
289 $params['expiry'],
290 $params['reason'],
291 $this->getBlockOptions( $params ),
292 $this->getRestrictions( $params ),
293 $params['tags']
294 )->placeBlock( $params['newblock'] ? BlockUser::CONFLICT_NEW : BlockUser::CONFLICT_FAIL );
295 }
296
298 public function mustBePosted() {
299 return true;
300 }
301
303 public function isWriteMode() {
304 return true;
305 }
306
308 public function getAllowedParams() {
309 $params = [
310 'id' => [ ParamValidator::PARAM_TYPE => 'integer' ],
311 'user' => [
312 ParamValidator::PARAM_TYPE => 'user',
313 UserDef::PARAM_ALLOWED_USER_TYPES => [ 'name', 'ip', 'temp', 'cidr', 'id' ],
314 UserDef::PARAM_RETURN_OBJECT => true,
315 ],
316 'userid' => [
317 ParamValidator::PARAM_TYPE => 'integer',
318 ParamValidator::PARAM_DEPRECATED => true,
319 ],
320 'expiry' => 'never',
321 'reason' => '',
322 'anononly' => false,
323 'nocreate' => false,
324 'autoblock' => false,
325 'noemail' => false,
326 'hidename' => false,
327 'allowusertalk' => false,
328 'reblock' => false,
329 'newblock' => false,
330 'watchuser' => false,
331 ];
332
333 // Params appear in the docs in the order they are defined,
334 // which is why this is here and not at the bottom.
335 if ( $this->watchlistExpiryEnabled ) {
336 $params += [
337 'watchlistexpiry' => [
338 ParamValidator::PARAM_TYPE => 'expiry',
339 ExpiryDef::PARAM_MAX => $this->watchlistMaxDuration,
340 ExpiryDef::PARAM_USE_MAX => true,
341 ]
342 ];
343 }
344
345 $pageLimit = $this->getConfig()->get( MainConfigNames::EnableMultiBlocks ) ? 50 : 10;
346
347 $params += [
348 'tags' => [
349 ParamValidator::PARAM_TYPE => 'tags',
350 ParamValidator::PARAM_ISMULTI => true,
351 ],
352 'partial' => false,
353 'pagerestrictions' => [
354 ParamValidator::PARAM_TYPE => 'title',
355 TitleDef::PARAM_MUST_EXIST => true,
356
357 // TODO: TitleDef returns instances of TitleValue when PARAM_RETURN_OBJECT is
358 // truthy. At the time of writing,
359 // MediaWiki\Block\Restriction\PageRestriction::newFromTitle accepts either
360 // string or instance of Title.
361 //TitleDef::PARAM_RETURN_OBJECT => true,
362
363 ParamValidator::PARAM_ISMULTI => true,
364 ParamValidator::PARAM_ISMULTI_LIMIT1 => $pageLimit,
365 ParamValidator::PARAM_ISMULTI_LIMIT2 => $pageLimit,
366 ],
367 'namespacerestrictions' => [
368 ParamValidator::PARAM_ISMULTI => true,
369 ParamValidator::PARAM_TYPE => 'namespace',
370 ],
371 'actionrestrictions' => [
372 ParamValidator::PARAM_ISMULTI => true,
373 ParamValidator::PARAM_TYPE => array_keys(
374 $this->blockActionInfo->getAllBlockActions()
375 ),
376 ],
377 ];
378
379 return $params;
380 }
381
383 public function needsToken() {
384 return 'csrf';
385 }
386
388 protected function getExamplesMessages() {
389 // phpcs:disable Generic.Files.LineLength
390 return [
391 'action=block&user=192.0.2.5&expiry=3%20days&reason=First%20strike&token=123ABC'
392 => 'apihelp-block-example-ip-simple',
393 'action=block&user=Vandal&expiry=never&reason=Vandalism&nocreate=&autoblock=&noemail=&token=123ABC'
394 => 'apihelp-block-example-user-complex',
395 ];
396 // phpcs:enable
397 }
398
400 public function getHelpUrls() {
401 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Block';
402 }
403}
404
406class_alias( ApiBlock::class, 'ApiBlock' );
const NS_USER
Definition Defines.php:53
This abstract class implements many basic API functions, and is the base of all API classes.
Definition ApiBase.php:60
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
getHookRunner()
Get an ApiHookRunner for running core API hooks.
Definition ApiBase.php:781
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:1070
getResult()
Get the result object.
Definition ApiBase.php:696
requireMaxOneParameter( $params,... $required)
Dies if more than one parameter from a certain set of parameters are set and not false.
Definition ApiBase.php:1012
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:975
API module that facilitates the blocking of users.
Definition ApiBlock.php:43
execute()
Blocks the user specified in the parameters for the given expiry, with the given reason,...
Definition ApiBlock.php:78
getHelpUrls()
Return links to more detailed help pages about the module.1.25, returning boolean false is deprecated...
Definition ApiBlock.php:400
getAllowedParams()
Returns an array of allowed parameters (parameter name) => (default value) or (parameter name) => (ar...
Definition ApiBlock.php:308
insertBlock( $target, $params)
Insert a block.
Definition ApiBlock.php:284
getExamplesMessages()
Returns usage examples for this module.Return value has query strings as keys, with values being eith...
Definition ApiBlock.php:388
__construct(ApiMain $main, string $action, private readonly BlockPermissionCheckerFactory $blockPermissionCheckerFactory, private readonly BlockUserFactory $blockUserFactory, private readonly UserIdentityLookup $userIdentityLookup, WatchedItemStoreInterface $watchedItemStore, private readonly BlockTargetFactory $blockTargetFactory, private readonly BlockActionInfo $blockActionInfo, private readonly DatabaseBlockStore $blockStore, WatchlistManager $watchlistManager, UserOptionsLookup $userOptionsLookup,)
Definition ApiBlock.php:47
mustBePosted()
Indicates whether this module must be called with a POST request.Implementations of this method must ...
Definition ApiBlock.php:298
isWriteMode()
Indicates whether this module requires write access to the wiki.API modules must override this method...
Definition ApiBlock.php:303
needsToken()
Returns the token type this module requires in order to execute.Modules are strongly encouraged to us...
Definition ApiBlock.php:383
This is the main API class, used for both external and internal processing.
Definition ApiMain.php:66
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:41
const CONFLICT_NEW
On conflict, create a new block.
Definition BlockUser.php:45
const CONFLICT_FAIL
On conflict, do not insert the block.
Definition BlockUser.php:43
A DatabaseBlock (unlike a SystemBlock) is stored in the database, may give rise to autoblocks and may...
Restriction for partial blocks of actions.
makeTitle( $linkId)
Convert a link ID to a Title.to override Title
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()
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:44
Represents a title within MediaWiki.
Definition Title.php:69
Provides access to user options.
Service for formatting and validating API parameters.
Type definition for expiry timestamps.
Definition ExpiryDef.php:18
trait ApiWatchlistTrait
An ApiWatchlistTrait adds class properties and convenience methods for APIs that allow you to watch a...
Service for looking up UserIdentity.
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.
getExpiryFromParams(array $params, ?PageIdentity $page=null, ?UserIdentity $user=null, string $userOption='watchdefault-expiry')
Get formatted expiry from the given parameters.
getWatchlistExpiry(WatchedItemStoreInterface $store, PageIdentity $page, UserIdentity $user)
Get existing expiry from the database.