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