MediaWiki master
ApiBlock.php
Go to the documentation of this file.
1<?php
9namespace MediaWiki\Api;
10
34use RuntimeException;
37
44class ApiBlock extends ApiBase {
45
47
48 private BlockPermissionCheckerFactory $blockPermissionCheckerFactory;
49 private BlockUserFactory $blockUserFactory;
50 private TitleFactory $titleFactory;
51 private UserIdentityLookup $userIdentityLookup;
52 private WatchedItemStoreInterface $watchedItemStore;
53 private BlockActionInfo $blockActionInfo;
54 private DatabaseBlockStore $blockStore;
55 private BlockTargetFactory $blockTargetFactory;
56
57 public function __construct(
58 ApiMain $main,
59 string $action,
60 BlockPermissionCheckerFactory $blockPermissionCheckerFactory,
61 BlockUserFactory $blockUserFactory,
62 TitleFactory $titleFactory,
63 UserIdentityLookup $userIdentityLookup,
64 WatchedItemStoreInterface $watchedItemStore,
65 BlockTargetFactory $blockTargetFactory,
66 BlockActionInfo $blockActionInfo,
67 DatabaseBlockStore $blockStore,
68 WatchlistManager $watchlistManager,
69 UserOptionsLookup $userOptionsLookup
70 ) {
71 parent::__construct( $main, $action );
72
73 $this->blockPermissionCheckerFactory = $blockPermissionCheckerFactory;
74 $this->blockUserFactory = $blockUserFactory;
75 $this->titleFactory = $titleFactory;
76 $this->userIdentityLookup = $userIdentityLookup;
77 $this->watchedItemStore = $watchedItemStore;
78 $this->blockTargetFactory = $blockTargetFactory;
79 $this->blockActionInfo = $blockActionInfo;
80 $this->blockStore = $blockStore;
81
82 // Variables needed in ApiWatchlistTrait trait
83 $this->watchlistExpiryEnabled = $this->getConfig()->get( MainConfigNames::WatchlistExpiry );
84 $this->watchlistMaxDuration =
86 $this->watchlistManager = $watchlistManager;
87 $this->userOptionsLookup = $userOptionsLookup;
88 }
89
96 public function execute() {
97 $this->checkUserRightsAny( 'block' );
98 $params = $this->extractRequestParams();
99 $this->requireOnlyOneParameter( $params, 'id', 'user', 'userid' );
100 $this->requireMaxOneParameter( $params, 'newblock', 'reblock' );
101 $this->requireNoConflictingParameters( $params,
102 'id', [ 'newblock', 'reblock' ] );
103
104 if ( $params['id'] !== null ) {
105 $block = $this->blockStore->newFromID( $params['id'], true );
106 if ( !$block ) {
107 $this->dieWithError(
108 [ 'apierror-nosuchblockid', $params['id'] ],
109 'nosuchblockid' );
110 }
111 if ( $block->getType() === AbstractBlock::TYPE_AUTO ) {
112 $this->dieWithError( 'apierror-modify-autoblock' );
113 }
114 $status = $this->updateBlock( $block, $params );
115 } else {
116 if ( $params['user'] !== null ) {
117 $target = $this->blockTargetFactory->newFromUser( $params['user'] );
118 } else {
119 $targetUser = $this->userIdentityLookup->getUserIdentityByUserId( $params['userid'] );
120 if ( !$targetUser ) {
121 $this->dieWithError( [ 'apierror-nosuchuserid', $params['userid'] ], 'nosuchuserid' );
122 }
123 $target = $this->blockTargetFactory->newUserBlockTarget( $targetUser );
124 }
125 if ( $params['newblock'] ) {
126 $status = $this->insertBlock( $target, $params );
127 } else {
128 $blocks = $this->blockStore->newListFromTarget(
129 $target, null, false, DatabaseBlockStore::AUTO_NONE );
130 if ( count( $blocks ) === 0 ) {
131 $status = $this->insertBlock( $target, $params );
132 } elseif ( count( $blocks ) === 1 ) {
133 if ( $params['reblock'] ) {
134 $status = $this->updateBlock( $blocks[0], $params );
135 } else {
136 $status = Status::newFatal( 'ipb_already_blocked', $blocks[0]->getTargetName() );
137 }
138 } else {
139 $this->dieWithError( 'apierror-ambiguous-block', 'ambiguous-block' );
140 }
141 }
142 }
143
144 if ( !$status->isOK() ) {
145 $this->dieStatus( $status );
146 }
147
148 $block = $status->value;
149 if ( !( $block instanceof DatabaseBlock ) ) {
150 throw new RuntimeException( "Unexpected block class" );
151 }
152
153 $userPage = Title::makeTitle( NS_USER, $block->getTargetName() );
154 $watchlistExpiry = $this->getExpiryFromParams( $params, $userPage, $this->getUser() );
155
156 if ( $params['watchuser'] && $block->getType() !== AbstractBlock::TYPE_RANGE ) {
157 $this->setWatch( 'watch', $userPage, $this->getUser(), null, $watchlistExpiry );
158 }
159
160 $res = [];
161
162 $res['user'] = $block->getTargetName();
163
164 $blockedUser = $block->getTargetUserIdentity();
165 $res['userID'] = $blockedUser ? $blockedUser->getId() : 0;
166
167 $res['expiry'] = ApiResult::formatExpiry( $block->getExpiry(), 'infinite' );
168 $res['id'] = $block->getId();
169
170 $res['reason'] = $params['reason'];
171 $res['anononly'] = $params['anononly'];
172 $res['nocreate'] = $params['nocreate'];
173 $res['autoblock'] = $params['autoblock'];
174 $res['noemail'] = $params['noemail'];
175 $res['hidename'] = $block->getHideName();
176 $res['allowusertalk'] = $params['allowusertalk'];
177 $res['watchuser'] = $params['watchuser'];
178 if ( $watchlistExpiry ) {
179 $expiry = $this->getWatchlistExpiry(
180 $this->watchedItemStore,
181 $userPage,
182 $this->getUser()
183 );
184 $res['watchlistexpiry'] = $expiry;
185 }
186 $res['partial'] = $params['partial'];
187 $res['pagerestrictions'] = $params['pagerestrictions'];
188 $res['namespacerestrictions'] = $params['namespacerestrictions'];
189 $res['actionrestrictions'] = $params['actionrestrictions'];
190
191 $this->getResult()->addValue( null, $this->getModuleName(), $res );
192 }
193
200 private function getBlockOptions( $params ) {
201 return [
202 'isCreateAccountBlocked' => $params['nocreate'],
203 'isEmailBlocked' => $params['noemail'],
204 'isHardBlock' => !$params['anononly'],
205 'isAutoblocking' => $params['autoblock'],
206 'isUserTalkEditBlocked' => !$params['allowusertalk'],
207 'isHideUser' => $params['hidename'],
208 'isPartial' => $params['partial'],
209 ];
210 }
211
217 private function getRestrictions( $params ) {
218 $restrictions = [];
219 if ( $params['partial'] ) {
220 $pageRestrictions = array_map(
221 PageRestriction::newFromTitle( ... ),
222 (array)$params['pagerestrictions']
223 );
224
225 $namespaceRestrictions = array_map( static function ( $id ) {
226 return new NamespaceRestriction( 0, $id );
227 }, (array)$params['namespacerestrictions'] );
228 $restrictions = array_merge( $pageRestrictions, $namespaceRestrictions );
229
230 $actionRestrictions = array_map( function ( $action ) {
231 return new ActionRestriction( 0, $this->blockActionInfo->getIdFromAction( $action ) );
232 }, (array)$params['actionrestrictions'] );
233 $restrictions = array_merge( $restrictions, $actionRestrictions );
234 }
235 return $restrictions;
236 }
237
243 private function checkEmailPermissions( $params ) {
244 if (
245 $params['noemail'] &&
246 !$this->blockPermissionCheckerFactory
247 ->newChecker( $this->getAuthority() )
248 ->checkEmailPermissions()
249 ) {
250 $this->dieWithError( 'apierror-cantblock-email' );
251 }
252 }
253
261 private function updateBlock( DatabaseBlock $block, $params ) {
262 $this->checkEmailPermissions( $params );
263 return $this->blockUserFactory->newUpdateBlock(
264 $block,
265 $this->getAuthority(),
266 $params['expiry'],
267 $params['reason'],
268 $this->getBlockOptions( $params ),
269 $this->getRestrictions( $params ),
270 $params['tags']
271 )->placeBlock();
272 }
273
281 private function insertBlock( $target, $params ) {
282 $this->checkEmailPermissions( $params );
283 return $this->blockUserFactory->newBlockUser(
284 $target,
285 $this->getAuthority(),
286 $params['expiry'],
287 $params['reason'],
288 $this->getBlockOptions( $params ),
289 $this->getRestrictions( $params ),
290 $params['tags']
291 )->placeBlock( $params['newblock'] ? BlockUser::CONFLICT_NEW : BlockUser::CONFLICT_FAIL );
292 }
293
295 public function mustBePosted() {
296 return true;
297 }
298
300 public function isWriteMode() {
301 return true;
302 }
303
305 public function getAllowedParams() {
306 $params = [
307 'id' => [ ParamValidator::PARAM_TYPE => 'integer' ],
308 'user' => [
309 ParamValidator::PARAM_TYPE => 'user',
310 UserDef::PARAM_ALLOWED_USER_TYPES => [ 'name', 'ip', 'temp', 'cidr', 'id' ],
311 UserDef::PARAM_RETURN_OBJECT => true,
312 ],
313 'userid' => [
314 ParamValidator::PARAM_TYPE => 'integer',
315 ParamValidator::PARAM_DEPRECATED => true,
316 ],
317 'expiry' => 'never',
318 'reason' => '',
319 'anononly' => false,
320 'nocreate' => false,
321 'autoblock' => false,
322 'noemail' => false,
323 'hidename' => false,
324 'allowusertalk' => false,
325 'reblock' => false,
326 'newblock' => false,
327 'watchuser' => false,
328 ];
329
330 // Params appear in the docs in the order they are defined,
331 // which is why this is here and not at the bottom.
332 if ( $this->watchlistExpiryEnabled ) {
333 $params += [
334 'watchlistexpiry' => [
335 ParamValidator::PARAM_TYPE => 'expiry',
336 ExpiryDef::PARAM_MAX => $this->watchlistMaxDuration,
337 ExpiryDef::PARAM_USE_MAX => true,
338 ]
339 ];
340 }
341
342 $pageLimit = $this->getConfig()->get( MainConfigNames::EnableMultiBlocks ) ? 50 : 10;
343
344 $params += [
345 'tags' => [
346 ParamValidator::PARAM_TYPE => 'tags',
347 ParamValidator::PARAM_ISMULTI => true,
348 ],
349 'partial' => false,
350 'pagerestrictions' => [
351 ParamValidator::PARAM_TYPE => 'title',
352 TitleDef::PARAM_MUST_EXIST => true,
353
354 // TODO: TitleDef returns instances of TitleValue when PARAM_RETURN_OBJECT is
355 // truthy. At the time of writing,
356 // MediaWiki\Block\Restriction\PageRestriction::newFromTitle accepts either
357 // string or instance of Title.
358 //TitleDef::PARAM_RETURN_OBJECT => true,
359
360 ParamValidator::PARAM_ISMULTI => true,
361 ParamValidator::PARAM_ISMULTI_LIMIT1 => $pageLimit,
362 ParamValidator::PARAM_ISMULTI_LIMIT2 => $pageLimit,
363 ],
364 'namespacerestrictions' => [
365 ParamValidator::PARAM_ISMULTI => true,
366 ParamValidator::PARAM_TYPE => 'namespace',
367 ],
368 'actionrestrictions' => [
369 ParamValidator::PARAM_ISMULTI => true,
370 ParamValidator::PARAM_TYPE => array_keys(
371 $this->blockActionInfo->getAllBlockActions()
372 ),
373 ],
374 ];
375
376 return $params;
377 }
378
380 public function needsToken() {
381 return 'csrf';
382 }
383
385 protected function getExamplesMessages() {
386 // phpcs:disable Generic.Files.LineLength
387 return [
388 'action=block&user=192.0.2.5&expiry=3%20days&reason=First%20strike&token=123ABC'
389 => 'apihelp-block-example-ip-simple',
390 'action=block&user=Vandal&expiry=never&reason=Vandalism&nocreate=&autoblock=&noemail=&token=123ABC'
391 => 'apihelp-block-example-user-complex',
392 ];
393 // phpcs:enable
394 }
395
397 public function getHelpUrls() {
398 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Block';
399 }
400}
401
403class_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:61
dieWithError( $msg, $code=null, $data=null, $httpCode=0)
Abort execution with an error.
Definition ApiBase.php:1511
checkUserRightsAny( $rights)
Helper function for permission-denied errors.
Definition ApiBase.php:1620
getModuleName()
Get the name of the module being executed by this instance.
Definition ApiBase.php:543
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:1056
getResult()
Get the result object.
Definition ApiBase.php:682
requireMaxOneParameter( $params,... $required)
Dies if more than one parameter from a certain set of parameters are set and not false.
Definition ApiBase.php:998
dieStatus(StatusValue $status)
Throw an ApiUsageException based on the Status object.
Definition ApiBase.php:1562
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition ApiBase.php:823
requireOnlyOneParameter( $params,... $required)
Die if 0 or more than one of a certain set of parameters is set and not false.
Definition ApiBase.php:961
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:96
getHelpUrls()
Return links to more detailed help pages about the module.1.25, returning boolean false is deprecated...
Definition ApiBlock.php:397
getAllowedParams()
Returns an array of allowed parameters (parameter name) => (default value) or (parameter name) => (ar...
Definition ApiBlock.php:305
getExamplesMessages()
Returns usage examples for this module.Return value has query strings as keys, with values being eith...
Definition ApiBlock.php:385
mustBePosted()
Indicates whether this module must be called with a POST request.Implementations of this method must ...
Definition ApiBlock.php:295
__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:57
isWriteMode()
Indicates whether this module requires write access to the wiki.API modules must override this method...
Definition ApiBlock.php:300
needsToken()
Returns the token type this module requires in order to execute.Modules are strongly encouraged to us...
Definition ApiBlock.php:380
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
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
Creates Title objects.
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.