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