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