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 BlockUtils $blockUtils;
68 private BlockActionInfo $blockActionInfo;
69 private DatabaseBlockStore $blockStore;
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 BlockUtils $blockUtils,
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->blockUtils = $blockUtils;
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' );
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 = $params['user'];
131 } else {
132 $target = $this->userIdentityLookup->getUserIdentityByUserId( $params['userid'] );
133 if ( !$target ) {
134 $this->dieWithError( [ 'apierror-nosuchuserid', $params['userid'] ], 'nosuchuserid' );
135 }
136 }
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() );
148 }
149 } else {
150 $this->dieWithError( 'apierror-ambiguous-block', 'ambiguous-block' );
151 }
152 }
153 }
154
155 if ( !$status->isOK() ) {
156 $this->dieStatus( $status );
157 }
158
159 $block = $status->value;
160 if ( !( $block instanceof DatabaseBlock ) ) {
161 throw new RuntimeException( "Unexpected block class" );
162 }
163
164 $watchlistExpiry = $this->getExpiryFromParams( $params );
165 $userPage = Title::makeTitle( NS_USER, $block->getTargetName() );
166
167 if ( $params['watchuser'] && $block->getType() !== AbstractBlock::TYPE_RANGE ) {
168 $this->setWatch( 'watch', $userPage, $this->getUser(), null, $watchlistExpiry );
169 }
170
171 $res = [];
172
173 $res['user'] = $block->getTargetName();
174
175 $blockedUser = $block->getTargetUserIdentity();
176 $res['userID'] = $blockedUser ? $blockedUser->getId() : 0;
177
178 $res['expiry'] = ApiResult::formatExpiry( $block->getExpiry(), 'infinite' );
179 $res['id'] = $block->getId();
180
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()
194 );
195 $res['watchlistexpiry'] = $expiry;
196 }
197 $res['partial'] = $params['partial'];
198 $res['pagerestrictions'] = $params['pagerestrictions'];
199 $res['namespacerestrictions'] = $params['namespacerestrictions'];
201 $res['actionrestrictions'] = $params['actionrestrictions'];
202 }
203
204 $this->getResult()->addValue( null, $this->getModuleName(), $res );
205 }
206
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'],
222 ];
223 }
224
230 private function getRestrictions( $params ) {
231 $restrictions = [];
232 if ( $params['partial'] ) {
233 $pageRestrictions = array_map(
234 [ PageRestriction::class, 'newFromTitle' ],
235 (array)$params['pagerestrictions']
236 );
237
238 $namespaceRestrictions = array_map( static function ( $id ) {
239 return new NamespaceRestriction( 0, $id );
240 }, (array)$params['namespacerestrictions'] );
241 $restrictions = array_merge( $pageRestrictions, $namespaceRestrictions );
242
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 );
248 }
249 }
250 return $restrictions;
251 }
252
258 private function checkEmailPermissions( $params ) {
259 if (
260 $params['noemail'] &&
261 !$this->blockPermissionCheckerFactory
262 ->newBlockPermissionChecker( null, $this->getAuthority() )
263 ->checkEmailPermissions()
264 ) {
265 $this->dieWithError( 'apierror-cantblock-email' );
266 }
267 }
268
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();
287 }
288
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 );
307 }
308
309 public function mustBePosted() {
310 return true;
311 }
312
313 public function isWriteMode() {
314 return true;
315 }
316
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' ],
323 ],
324 'userid' => [
325 ParamValidator::PARAM_TYPE => 'integer',
326 ParamValidator::PARAM_DEPRECATED => true,
327 ],
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,
339 ];
340
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,
349 ]
350 ];
351 }
352
353 $params += [
354 'tags' => [
355 ParamValidator::PARAM_TYPE => 'tags',
356 ParamValidator::PARAM_ISMULTI => true,
357 ],
358 'partial' => false,
359 'pagerestrictions' => [
360 ParamValidator::PARAM_TYPE => 'title',
361 TitleDef::PARAM_MUST_EXIST => true,
362
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,
368
369 ParamValidator::PARAM_ISMULTI => true,
370 ParamValidator::PARAM_ISMULTI_LIMIT1 => 10,
371 ParamValidator::PARAM_ISMULTI_LIMIT2 => 10,
372 ],
373 'namespacerestrictions' => [
374 ParamValidator::PARAM_ISMULTI => true,
375 ParamValidator::PARAM_TYPE => 'namespace',
376 ],
377 ];
378
380 $params += [
381 'actionrestrictions' => [
382 ParamValidator::PARAM_ISMULTI => true,
383 ParamValidator::PARAM_TYPE => array_keys(
384 $this->blockActionInfo->getAllBlockActions()
385 ),
386 ],
387 ];
388 }
389
390 return $params;
391 }
392
393 public function needsToken() {
394 return 'csrf';
395 }
396
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',
404 ];
405 // phpcs:enable
406 }
407
408 public function getHelpUrls() {
409 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Block';
410 }
411}
412
414class_alias( ApiBlock::class, 'ApiBlock' );
const NS_USER
Definition Defines.php:67
array $params
The job parameters.
This abstract class implements many basic API functions, and is the base of all API classes.
Definition ApiBase.php:76
dieWithError( $msg, $code=null, $data=null, $httpCode=0)
Abort execution with an error.
Definition ApiBase.php:1565
checkUserRightsAny( $rights)
Helper function for permission-denied errors.
Definition ApiBase.php:1680
getModuleName()
Get the name of the module being executed by this instance.
Definition ApiBase.php:571
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:1081
getResult()
Get the result object.
Definition ApiBase.php:710
dieStatus(StatusValue $status)
Throw an ApiUsageException based on the Status object.
Definition ApiBase.php:1620
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition ApiBase.php:851
requireOnlyOneParameter( $params,... $required)
Die if 0 or more than one of a certain set of parameters is set and not false.
Definition ApiBase.php:990
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:408
getAllowedParams()
Returns an array of allowed parameters (parameter name) => (default value) or (parameter name) => (ar...
Definition ApiBlock.php:317
__construct(ApiMain $main, string $action, BlockPermissionCheckerFactory $blockPermissionCheckerFactory, BlockUserFactory $blockUserFactory, TitleFactory $titleFactory, UserIdentityLookup $userIdentityLookup, WatchedItemStoreInterface $watchedItemStore, BlockUtils $blockUtils, BlockActionInfo $blockActionInfo, DatabaseBlockStore $blockStore, WatchlistManager $watchlistManager, UserOptionsLookup $userOptionsLookup)
Definition ApiBlock.php:71
getExamplesMessages()
Returns usage examples for this module.
Definition ApiBlock.php:397
mustBePosted()
Indicates whether this module must be called with a POST request.
Definition ApiBlock.php:309
isWriteMode()
Indicates whether this module requires write access to the wiki.
Definition ApiBlock.php:313
needsToken()
Returns the token type this module requires in order to execute.
Definition ApiBlock.php:393
This is the main API class, used for both external and internal processing.
Definition ApiMain.php:78
static formatExpiry( $expiry, $infinity='infinity')
Format an expiry timestamp for API output.
Defines the actions that can be blocked by a partial block.
Handles the backend logic of blocking users.
Definition BlockUser.php:54
const CONFLICT_NEW
On conflict, create a new block.
Definition BlockUser.php:58
Backend class for blocking utils.
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 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.
Interface for objects representing user identity.
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.