Merge "Prevent blocked users from performing revision deletion"
[lhc/web/wiklou.git] / includes / api / ApiRevisionDelete.php
1 <?php
2 /**
3 * Created on Jun 25, 2013
4 *
5 * Copyright © 2013 Brad Jorsch <bjorsch@wikimedia.org>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 * http://www.gnu.org/copyleft/gpl.html
21 *
22 * @file
23 * @since 1.23
24 */
25
26 /**
27 * API interface to RevDel. The API equivalent of Special:RevisionDelete.
28 * Requires API write mode to be enabled.
29 *
30 * @ingroup API
31 */
32 class ApiRevisionDelete extends ApiBase {
33
34 public function execute() {
35 $this->useTransactionalTimeLimit();
36
37 $params = $this->extractRequestParams();
38 $user = $this->getUser();
39
40 if ( !$user->isAllowed( RevisionDeleter::getRestriction( $params['type'] ) ) ) {
41 $this->dieUsageMsg( 'badaccess-group0' );
42 }
43
44 if ( $user->isBlocked() ) {
45 $block = $user->getBlock();
46
47 // Die using the appropriate message depending on block type
48 if ( $block->getType() == TYPE_AUTO ) {
49 $this->dieUsage(
50 'Your IP address has been blocked automatically, because it was used by a blocked user',
51 'autoblocked',
52 0,
53 array( 'blockinfo' => ApiQueryUserInfo::getBlockInfo( $block ) )
54 );
55 } else {
56 $this->dieUsage(
57 'You have been blocked from editing',
58 'blocked',
59 0,
60 array( 'blockinfo' => ApiQueryUserInfo::getBlockInfo( $block ) )
61 );
62 }
63 }
64
65 if ( !$params['ids'] ) {
66 $this->dieUsage( "At least one value is required for 'ids'", 'badparams' );
67 }
68
69 $hide = $params['hide'] ?: array();
70 $show = $params['show'] ?: array();
71 if ( array_intersect( $hide, $show ) ) {
72 $this->dieUsage( "Mutually exclusive values for 'hide' and 'show'", 'badparams' );
73 } elseif ( !$hide && !$show ) {
74 $this->dieUsage( "At least one value is required for 'hide' or 'show'", 'badparams' );
75 }
76 $bits = array(
77 'content' => RevisionDeleter::getRevdelConstant( $params['type'] ),
78 'comment' => Revision::DELETED_COMMENT,
79 'user' => Revision::DELETED_USER,
80 );
81 $bitfield = array();
82 foreach ( $bits as $key => $bit ) {
83 if ( in_array( $key, $hide ) ) {
84 $bitfield[$bit] = 1;
85 } elseif ( in_array( $key, $show ) ) {
86 $bitfield[$bit] = 0;
87 } else {
88 $bitfield[$bit] = -1;
89 }
90 }
91
92 if ( $params['suppress'] === 'yes' ) {
93 if ( !$user->isAllowed( 'suppressrevision' ) ) {
94 $this->dieUsageMsg( 'badaccess-group0' );
95 }
96 $bitfield[Revision::DELETED_RESTRICTED] = 1;
97 } elseif ( $params['suppress'] === 'no' ) {
98 $bitfield[Revision::DELETED_RESTRICTED] = 0;
99 } else {
100 $bitfield[Revision::DELETED_RESTRICTED] = -1;
101 }
102
103 $targetObj = null;
104 if ( $params['target'] ) {
105 $targetObj = Title::newFromText( $params['target'] );
106 }
107 $targetObj = RevisionDeleter::suggestTarget( $params['type'], $targetObj, $params['ids'] );
108 if ( $targetObj === null ) {
109 $this->dieUsage( 'A target title is required for this RevDel type', 'needtarget' );
110 }
111
112 $list = RevisionDeleter::createList(
113 $params['type'], $this->getContext(), $targetObj, $params['ids']
114 );
115 $status = $list->setVisibility(
116 array( 'value' => $bitfield, 'comment' => $params['reason'], 'perItemStatus' => true )
117 );
118
119 $result = $this->getResult();
120 $data = $this->extractStatusInfo( $status );
121 $data['target'] = $targetObj->getFullText();
122 $data['items'] = array();
123
124 foreach ( $status->itemStatuses as $id => $s ) {
125 $data['items'][$id] = $this->extractStatusInfo( $s );
126 $data['items'][$id]['id'] = $id;
127 }
128
129 $list->reloadFromMaster();
130 // @codingStandardsIgnoreStart Avoid function calls in a FOR loop test part
131 for ( $item = $list->reset(); $list->current(); $item = $list->next() ) {
132 $data['items'][$item->getId()] += $item->getApiData( $this->getResult() );
133 }
134 // @codingStandardsIgnoreEnd
135
136 $data['items'] = array_values( $data['items'] );
137 ApiResult::setIndexedTagName( $data['items'], 'i' );
138 $result->addValue( null, $this->getModuleName(), $data );
139 }
140
141 private function extractStatusInfo( $status ) {
142 $ret = array(
143 'status' => $status->isOK() ? 'Success' : 'Fail',
144 );
145 $errors = $this->formatStatusMessages( $status->getErrorsByType( 'error' ) );
146 if ( $errors ) {
147 ApiResult::setIndexedTagName( $errors, 'e' );
148 $ret['errors'] = $errors;
149 }
150 $warnings = $this->formatStatusMessages( $status->getErrorsByType( 'warning' ) );
151 if ( $warnings ) {
152 ApiResult::setIndexedTagName( $warnings, 'w' );
153 $ret['warnings'] = $warnings;
154 }
155
156 return $ret;
157 }
158
159 private function formatStatusMessages( $messages ) {
160 if ( !$messages ) {
161 return array();
162 }
163 $ret = array();
164 foreach ( $messages as $m ) {
165 if ( $m['message'] instanceof Message ) {
166 $msg = $m['message'];
167 $message = array( 'message' => $msg->getKey() );
168 if ( $msg->getParams() ) {
169 $message['params'] = $msg->getParams();
170 ApiResult::setIndexedTagName( $message['params'], 'p' );
171 }
172 } else {
173 $message = array( 'message' => $m['message'] );
174 $msg = wfMessage( $m['message'] );
175 if ( isset( $m['params'] ) ) {
176 $message['params'] = $m['params'];
177 ApiResult::setIndexedTagName( $message['params'], 'p' );
178 $msg->params( $m['params'] );
179 }
180 }
181 $message['rendered'] = $msg->useDatabase( false )->inLanguage( 'en' )->plain();
182 $ret[] = $message;
183 }
184
185 return $ret;
186 }
187
188 public function mustBePosted() {
189 return true;
190 }
191
192 public function isWriteMode() {
193 return true;
194 }
195
196 public function getAllowedParams() {
197 return array(
198 'type' => array(
199 ApiBase::PARAM_TYPE => RevisionDeleter::getTypes(),
200 ApiBase::PARAM_REQUIRED => true
201 ),
202 'target' => null,
203 'ids' => array(
204 ApiBase::PARAM_ISMULTI => true,
205 ApiBase::PARAM_REQUIRED => true
206 ),
207 'hide' => array(
208 ApiBase::PARAM_TYPE => array( 'content', 'comment', 'user' ),
209 ApiBase::PARAM_ISMULTI => true,
210 ),
211 'show' => array(
212 ApiBase::PARAM_TYPE => array( 'content', 'comment', 'user' ),
213 ApiBase::PARAM_ISMULTI => true,
214 ),
215 'suppress' => array(
216 ApiBase::PARAM_TYPE => array( 'yes', 'no', 'nochange' ),
217 ApiBase::PARAM_DFLT => 'nochange',
218 ),
219 'reason' => null,
220 );
221 }
222
223 public function needsToken() {
224 return 'csrf';
225 }
226
227 protected function getExamplesMessages() {
228 return array(
229 'action=revisiondelete&target=Main%20Page&type=revision&ids=12345&' .
230 'hide=content&token=123ABC'
231 => 'apihelp-revisiondelete-example-revision',
232 'action=revisiondelete&type=logging&ids=67890&hide=content|comment|user&' .
233 'reason=BLP%20violation&token=123ABC'
234 => 'apihelp-revisiondelete-example-log',
235 );
236 }
237
238 public function getHelpUrls() {
239 return 'https://www.mediawiki.org/wiki/API:Revisiondelete';
240 }
241 }