MediaWiki master
ApiTag.php
Go to the documentation of this file.
1<?php
2
22namespace MediaWiki\Api;
23
24use ChangeTags;
27use RecentChange;
31
36class ApiTag extends ApiBase {
37
39
40 private IDatabase $dbr;
41 private RevisionStore $revisionStore;
42 private ChangeTagsStore $changeTagsStore;
43
51 public function __construct(
52 ApiMain $main,
53 $action,
54 IConnectionProvider $dbProvider,
55 RevisionStore $revisionStore,
56 ChangeTagsStore $changeTagsStore
57 ) {
58 parent::__construct( $main, $action );
59 $this->dbr = $dbProvider->getReplicaDatabase();
60 $this->revisionStore = $revisionStore;
61 $this->changeTagsStore = $changeTagsStore;
62 }
63
64 public function execute() {
66 $user = $this->getUser();
67
68 // make sure the user is allowed
69 $this->checkUserRightsAny( 'changetags' );
70
71 // Fail early if the user is sitewide blocked.
72 $block = $user->getBlock();
73 if ( $block && $block->isSitewide() ) {
74 $this->dieBlocked( $block );
75 }
76
77 // Check if user can add tags
78 if ( $params['tags'] ) {
79 $ableToTag = ChangeTags::canAddTagsAccompanyingChange( $params['tags'], $this->getAuthority() );
80 if ( !$ableToTag->isOK() ) {
81 $this->dieStatus( $ableToTag );
82 }
83 }
84
85 // validate and process each revid, rcid and logid
86 $this->requireAtLeastOneParameter( $params, 'revid', 'rcid', 'logid' );
87 $ret = [];
88 if ( $params['revid'] ) {
89 foreach ( $params['revid'] as $id ) {
90 $ret[] = $this->processIndividual( 'revid', $params, $id );
91 }
92 }
93 if ( $params['rcid'] ) {
94 foreach ( $params['rcid'] as $id ) {
95 $ret[] = $this->processIndividual( 'rcid', $params, $id );
96 }
97 }
98 if ( $params['logid'] ) {
99 foreach ( $params['logid'] as $id ) {
100 $ret[] = $this->processIndividual( 'logid', $params, $id );
101 }
102 }
103
104 ApiResult::setIndexedTagName( $ret, 'result' );
105 $this->getResult()->addValue( null, $this->getModuleName(), $ret );
106 }
107
108 protected function validateLogId( $logid ) {
109 $result = $this->dbr->newSelectQueryBuilder()
110 ->select( 'log_id' )
111 ->from( 'logging' )
112 ->where( [ 'log_id' => $logid ] )
113 ->caller( __METHOD__ )->fetchField();
114 return (bool)$result;
115 }
116
117 protected function processIndividual( $type, $params, $id ) {
118 $user = $this->getUser();
119 $idResult = [ $type => $id ];
120
121 // validate the ID
122 $valid = false;
123 switch ( $type ) {
124 case 'rcid':
125 $valid = RecentChange::newFromId( $id );
126 // TODO: replace use of PermissionManager
127 if ( $valid && $this->getPermissionManager()->isBlockedFrom( $user, $valid->getTitle() ) ) {
128 $idResult['status'] = 'error';
129 // @phan-suppress-next-line PhanTypeMismatchArgument
130 $idResult += $this->getErrorFormatter()->formatMessage( ApiMessage::create(
131 'apierror-blocked',
132 'blocked',
133 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable Block is checked and not null
134 [ 'blockinfo' => $this->getBlockDetails( $user->getBlock() ) ]
135 ) );
136 return $idResult;
137 }
138 break;
139 case 'revid':
140 $valid = $this->revisionStore->getRevisionById( $id );
141 // TODO: replace use of PermissionManager
142 if (
143 $valid &&
144 $this->getPermissionManager()->isBlockedFrom( $user, $valid->getPageAsLinkTarget() )
145 ) {
146 $idResult['status'] = 'error';
147 // @phan-suppress-next-line PhanTypeMismatchArgument
148 $idResult += $this->getErrorFormatter()->formatMessage( ApiMessage::create(
149 'apierror-blocked',
150 'blocked',
151 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable Block is checked and not null
152 [ 'blockinfo' => $this->getBlockDetails( $user->getBlock() ) ]
153 ) );
154 return $idResult;
155 }
156 break;
157 case 'logid':
158 $valid = $this->validateLogId( $id );
159 break;
160 }
161
162 if ( !$valid ) {
163 $idResult['status'] = 'error';
164 // Messages: apierror-nosuchrcid apierror-nosuchrevid apierror-nosuchlogid
165 $idResult += $this->getErrorFormatter()->formatMessage( [ "apierror-nosuch$type", $id ] );
166 return $idResult;
167 }
168
170 $params['remove'],
171 ( $type === 'rcid' ? $id : null ),
172 ( $type === 'revid' ? $id : null ),
173 ( $type === 'logid' ? $id : null ),
174 null,
175 $params['reason'],
176 $this->getAuthority()
177 );
178
179 if ( !$status->isOK() ) {
180 if ( $status->hasMessage( 'actionthrottledtext' ) ) {
181 $idResult['status'] = 'skipped';
182 } else {
183 $idResult['status'] = 'failure';
184 $idResult['errors'] = $this->getErrorFormatter()->arrayFromStatus( $status, 'error' );
185 }
186 } else {
187 $idResult['status'] = 'success';
188 if ( $status->value->logId === null ) {
189 $idResult['noop'] = true;
190 } else {
191 $idResult['actionlogid'] = $status->value->logId;
192 $idResult['added'] = $status->value->addedTags;
193 ApiResult::setIndexedTagName( $idResult['added'], 't' );
194 $idResult['removed'] = $status->value->removedTags;
195 ApiResult::setIndexedTagName( $idResult['removed'], 't' );
196
197 if ( $params['tags'] ) {
198 $this->changeTagsStore->addTags( $params['tags'], null, null, $status->value->logId );
199 }
200 }
201 }
202 return $idResult;
203 }
204
205 public function mustBePosted() {
206 return true;
207 }
208
209 public function isWriteMode() {
210 return true;
211 }
212
213 public function getAllowedParams() {
214 return [
215 'rcid' => [
216 ParamValidator::PARAM_TYPE => 'integer',
217 ParamValidator::PARAM_ISMULTI => true,
218 ],
219 'revid' => [
220 ParamValidator::PARAM_TYPE => 'integer',
221 ParamValidator::PARAM_ISMULTI => true,
222 ],
223 'logid' => [
224 ParamValidator::PARAM_TYPE => 'integer',
225 ParamValidator::PARAM_ISMULTI => true,
226 ],
227 'add' => [
228 ParamValidator::PARAM_TYPE => 'tags',
229 ParamValidator::PARAM_ISMULTI => true,
230 ],
231 'remove' => [
232 ParamValidator::PARAM_TYPE => 'string',
233 ParamValidator::PARAM_ISMULTI => true,
234 ],
235 'reason' => [
236 ParamValidator::PARAM_TYPE => 'string',
237 ParamValidator::PARAM_DEFAULT => '',
238 ],
239 'tags' => [
240 ParamValidator::PARAM_TYPE => 'tags',
241 ParamValidator::PARAM_ISMULTI => true,
242 ],
243 ];
244 }
245
246 public function needsToken() {
247 return 'csrf';
248 }
249
250 protected function getExamplesMessages() {
251 return [
252 'action=tag&revid=123&add=vandalism&token=123ABC'
253 => 'apihelp-tag-example-rev',
254 'action=tag&logid=123&remove=spam&reason=Wrongly+applied&token=123ABC'
255 => 'apihelp-tag-example-log',
256 ];
257 }
258
259 public function getHelpUrls() {
260 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Tag';
261 }
262}
263
265class_alias( ApiTag::class, 'ApiTag' );
array $params
The job parameters.
Recent changes tagging.
static updateTagsWithChecks( $tagsToAdd, $tagsToRemove, $rc_id, $rev_id, $log_id, $params, string $reason, Authority $performer)
Adds and/or removes tags to/from a given change, checking whether it is allowed first,...
static canAddTagsAccompanyingChange(array $tags, Authority $performer=null, $checkBlock=true)
Is it OK to allow the user to apply all the specified tags at the same time as they edit/make the cha...
This abstract class implements many basic API functions, and is the base of all API classes.
Definition ApiBase.php:76
checkUserRightsAny( $rights)
Helper function for permission-denied errors.
Definition ApiBase.php:1692
getModuleName()
Get the name of the module being executed by this instance.
Definition ApiBase.php:571
requireAtLeastOneParameter( $params,... $required)
Die if 0 of a certain set of parameters is set and not false.
Definition ApiBase.php:1050
getResult()
Get the result object.
Definition ApiBase.php:710
dieBlocked(Block $block)
Throw an ApiUsageException, which will (if uncaught) call the main module's error handler and die wit...
Definition ApiBase.php:1605
dieStatus(StatusValue $status)
Throw an ApiUsageException based on the Status object.
Definition ApiBase.php:1632
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition ApiBase.php:851
getPermissionManager()
Obtain a PermissionManager instance that subclasses may use in their authorization checks.
Definition ApiBase.php:770
This is the main API class, used for both external and internal processing.
Definition ApiMain.php:78
static create( $msg, $code=null, array $data=null)
Create an IApiMessage for the message.
static setIndexedTagName(array &$arr, $tag)
Set the tag name for numeric-keyed values in XML format.
getExamplesMessages()
Returns usage examples for this module.
Definition ApiTag.php:250
validateLogId( $logid)
Definition ApiTag.php:108
needsToken()
Returns the token type this module requires in order to execute.
Definition ApiTag.php:246
getHelpUrls()
Return links to more detailed help pages about the module.
Definition ApiTag.php:259
isWriteMode()
Indicates whether this module requires write access to the wiki.
Definition ApiTag.php:209
mustBePosted()
Indicates whether this module must be called with a POST request.
Definition ApiTag.php:205
execute()
Evaluates the parameters, performs the requested query, and sets up the result.
Definition ApiTag.php:64
getAllowedParams()
Returns an array of allowed parameters (parameter name) => (default value) or (parameter name) => (ar...
Definition ApiTag.php:213
processIndividual( $type, $params, $id)
Definition ApiTag.php:117
__construct(ApiMain $main, $action, IConnectionProvider $dbProvider, RevisionStore $revisionStore, ChangeTagsStore $changeTagsStore)
Definition ApiTag.php:51
Read-write access to the change_tags table.
Service for looking up page revisions.
Utility class for creating and reading rows in the recentchanges table.
Service for formatting and validating API parameters.
Provide primary and replica IDatabase connections.
getReplicaDatabase( $domain=false, $group=null)
Get connection to a replica database.
Interface to a relational database.
Definition IDatabase.php:48