Localisation updates from https://translatewiki.net.
[mediawiki.git] / includes / api / ApiTag.php
blob6ca0b6bc76c7cab4769aee2227b10083e65659c9
1 <?php
3 /**
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License along
15 * with this program; if not, write to the Free Software Foundation, Inc.,
16 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 * http://www.gnu.org/copyleft/gpl.html
19 * @file
22 namespace MediaWiki\Api;
24 use ChangeTags;
25 use MediaWiki\ChangeTags\ChangeTagsStore;
26 use MediaWiki\Revision\RevisionStore;
27 use RecentChange;
28 use Wikimedia\ParamValidator\ParamValidator;
29 use Wikimedia\Rdbms\IConnectionProvider;
30 use Wikimedia\Rdbms\IDatabase;
32 /**
33 * @ingroup API
34 * @since 1.25
36 class ApiTag extends ApiBase {
38 use ApiBlockInfoTrait;
40 private IDatabase $dbr;
41 private RevisionStore $revisionStore;
42 private ChangeTagsStore $changeTagsStore;
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;
57 public function execute() {
58 $params = $this->extractRequestParams();
59 $user = $this->getUser();
61 // make sure the user is allowed
62 $this->checkUserRightsAny( 'changetags' );
64 // Fail early if the user is sitewide blocked.
65 $block = $user->getBlock();
66 if ( $block && $block->isSitewide() ) {
67 $this->dieBlocked( $block );
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 );
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 );
86 if ( $params['rcid'] ) {
87 foreach ( $params['rcid'] as $id ) {
88 $ret[] = $this->processIndividual( 'rcid', $params, $id );
91 if ( $params['logid'] ) {
92 foreach ( $params['logid'] as $id ) {
93 $ret[] = $this->processIndividual( 'logid', $params, $id );
97 ApiResult::setIndexedTagName( $ret, 'result' );
98 $this->getResult()->addValue( null, $this->getModuleName(), $ret );
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;
110 protected function processIndividual( $type, $params, $id ) {
111 $user = $this->getUser();
112 $idResult = [ $type => $id ];
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;
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() )
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;
149 break;
150 case 'logid':
151 $valid = $this->validateLogId( $id );
152 break;
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;
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()
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' );
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' );
190 if ( $params['tags'] ) {
191 $this->changeTagsStore->addTags( $params['tags'], null, null, $status->value->logId );
195 return $idResult;
198 public function mustBePosted() {
199 return true;
202 public function isWriteMode() {
203 return true;
206 public function getAllowedParams() {
207 return [
208 'rcid' => [
209 ParamValidator::PARAM_TYPE => 'integer',
210 ParamValidator::PARAM_ISMULTI => true,
212 'revid' => [
213 ParamValidator::PARAM_TYPE => 'integer',
214 ParamValidator::PARAM_ISMULTI => true,
216 'logid' => [
217 ParamValidator::PARAM_TYPE => 'integer',
218 ParamValidator::PARAM_ISMULTI => true,
220 'add' => [
221 ParamValidator::PARAM_TYPE => 'tags',
222 ParamValidator::PARAM_ISMULTI => true,
224 'remove' => [
225 ParamValidator::PARAM_TYPE => 'string',
226 ParamValidator::PARAM_ISMULTI => true,
228 'reason' => [
229 ParamValidator::PARAM_TYPE => 'string',
230 ParamValidator::PARAM_DEFAULT => '',
232 'tags' => [
233 ParamValidator::PARAM_TYPE => 'tags',
234 ParamValidator::PARAM_ISMULTI => true,
239 public function needsToken() {
240 return 'csrf';
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',
252 public function getHelpUrls() {
253 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Tag';
257 /** @deprecated class alias since 1.43 */
258 class_alias( ApiTag::class, 'ApiTag' );