Merge "output: Narrow Title type hint to LinkTarget"
[lhc/web/wiklou.git] / includes / api / ApiTag.php
1 <?php
2
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.
8 *
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.
13 *
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
18 *
19 * @file
20 */
21
22 use MediaWiki\MediaWikiServices;
23
24 /**
25 * @ingroup API
26 * @since 1.25
27 */
28 class ApiTag extends ApiBase {
29
30 use ApiBlockInfoTrait;
31
32 /** @var \MediaWiki\Revision\RevisionStore */
33 private $revisionStore;
34
35 public function execute() {
36 $this->revisionStore = MediaWikiServices::getInstance()->getRevisionStore();
37
38 $params = $this->extractRequestParams();
39 $user = $this->getUser();
40
41 // make sure the user is allowed
42 $this->checkUserRightsAny( 'changetags' );
43
44 // Fail early if the user is sitewide blocked.
45 $block = $user->getBlock();
46 if ( $block && $block->isSitewide() ) {
47 $this->dieBlocked( $block );
48 }
49
50 // Check if user can add tags
51 if ( $params['tags'] ) {
52 $ableToTag = ChangeTags::canAddTagsAccompanyingChange( $params['tags'], $user );
53 if ( !$ableToTag->isOK() ) {
54 $this->dieStatus( $ableToTag );
55 }
56 }
57
58 // validate and process each revid, rcid and logid
59 $this->requireAtLeastOneParameter( $params, 'revid', 'rcid', 'logid' );
60 $ret = [];
61 if ( $params['revid'] ) {
62 foreach ( $params['revid'] as $id ) {
63 $ret[] = $this->processIndividual( 'revid', $params, $id );
64 }
65 }
66 if ( $params['rcid'] ) {
67 foreach ( $params['rcid'] as $id ) {
68 $ret[] = $this->processIndividual( 'rcid', $params, $id );
69 }
70 }
71 if ( $params['logid'] ) {
72 foreach ( $params['logid'] as $id ) {
73 $ret[] = $this->processIndividual( 'logid', $params, $id );
74 }
75 }
76
77 ApiResult::setIndexedTagName( $ret, 'result' );
78 $this->getResult()->addValue( null, $this->getModuleName(), $ret );
79 }
80
81 protected static function validateLogId( $logid ) {
82 $dbr = wfGetDB( DB_REPLICA );
83 $result = $dbr->selectField( 'logging', 'log_id', [ 'log_id' => $logid ],
84 __METHOD__ );
85 return (bool)$result;
86 }
87
88 protected function processIndividual( $type, $params, $id ) {
89 $user = $this->getUser();
90 $idResult = [ $type => $id ];
91
92 // validate the ID
93 $valid = false;
94 switch ( $type ) {
95 case 'rcid':
96 $valid = RecentChange::newFromId( $id );
97 if ( $valid && $this->getPermissionManager()->isBlockedFrom( $user, $valid->getTitle() ) ) {
98 $idResult['status'] = 'error';
99 $idResult += $this->getErrorFormatter()->formatMessage( ApiMessage::create(
100 'apierror-blocked',
101 'blocked',
102 [ 'blockinfo' => $this->getBlockDetails( $user->getBlock() ) ]
103 ) );
104 return $idResult;
105 }
106 break;
107 case 'revid':
108 $valid = $this->revisionStore->getRevisionById( $id );
109 if (
110 $valid &&
111 $this->getPermissionManager()->isBlockedFrom( $user, $valid->getPageAsLinkTarget() )
112 ) {
113 $idResult['status'] = 'error';
114 $idResult += $this->getErrorFormatter()->formatMessage( ApiMessage::create(
115 'apierror-blocked',
116 'blocked',
117 [ 'blockinfo' => $this->getBlockDetails( $user->getBlock() ) ]
118 ) );
119 return $idResult;
120 }
121 break;
122 case 'logid':
123 $valid = self::validateLogId( $id );
124 break;
125 }
126
127 if ( !$valid ) {
128 $idResult['status'] = 'error';
129 // Messages: apierror-nosuchrcid apierror-nosuchrevid apierror-nosuchlogid
130 $idResult += $this->getErrorFormatter()->formatMessage( [ "apierror-nosuch$type", $id ] );
131 return $idResult;
132 }
133
134 $status = ChangeTags::updateTagsWithChecks( $params['add'],
135 $params['remove'],
136 ( $type === 'rcid' ? $id : null ),
137 ( $type === 'revid' ? $id : null ),
138 ( $type === 'logid' ? $id : null ),
139 null,
140 $params['reason'],
141 $this->getUser() );
142
143 if ( !$status->isOK() ) {
144 if ( $status->hasMessage( 'actionthrottledtext' ) ) {
145 $idResult['status'] = 'skipped';
146 } else {
147 $idResult['status'] = 'failure';
148 $idResult['errors'] = $this->getErrorFormatter()->arrayFromStatus( $status, 'error' );
149 }
150 } else {
151 $idResult['status'] = 'success';
152 if ( is_null( $status->value->logId ) ) {
153 $idResult['noop'] = true;
154 } else {
155 $idResult['actionlogid'] = $status->value->logId;
156 $idResult['added'] = $status->value->addedTags;
157 ApiResult::setIndexedTagName( $idResult['added'], 't' );
158 $idResult['removed'] = $status->value->removedTags;
159 ApiResult::setIndexedTagName( $idResult['removed'], 't' );
160
161 if ( $params['tags'] ) {
162 ChangeTags::addTags( $params['tags'], null, null, $status->value->logId );
163 }
164 }
165 }
166 return $idResult;
167 }
168
169 public function mustBePosted() {
170 return true;
171 }
172
173 public function isWriteMode() {
174 return true;
175 }
176
177 public function getAllowedParams() {
178 return [
179 'rcid' => [
180 ApiBase::PARAM_TYPE => 'integer',
181 ApiBase::PARAM_ISMULTI => true,
182 ],
183 'revid' => [
184 ApiBase::PARAM_TYPE => 'integer',
185 ApiBase::PARAM_ISMULTI => true,
186 ],
187 'logid' => [
188 ApiBase::PARAM_TYPE => 'integer',
189 ApiBase::PARAM_ISMULTI => true,
190 ],
191 'add' => [
192 ApiBase::PARAM_TYPE => 'tags',
193 ApiBase::PARAM_ISMULTI => true,
194 ],
195 'remove' => [
196 ApiBase::PARAM_TYPE => 'string',
197 ApiBase::PARAM_ISMULTI => true,
198 ],
199 'reason' => [
200 ApiBase::PARAM_DFLT => '',
201 ],
202 'tags' => [
203 ApiBase::PARAM_TYPE => 'tags',
204 ApiBase::PARAM_ISMULTI => true,
205 ],
206 ];
207 }
208
209 public function needsToken() {
210 return 'csrf';
211 }
212
213 protected function getExamplesMessages() {
214 return [
215 'action=tag&revid=123&add=vandalism&token=123ABC'
216 => 'apihelp-tag-example-rev',
217 'action=tag&logid=123&remove=spam&reason=Wrongly+applied&token=123ABC'
218 => 'apihelp-tag-example-log',
219 ];
220 }
221
222 public function getHelpUrls() {
223 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Tag';
224 }
225 }