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