Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
79.00% |
79 / 100 |
|
50.00% |
5 / 10 |
CRAP | |
0.00% |
0 / 1 |
ApiRollback | |
79.00% |
79 / 100 |
|
50.00% |
5 / 10 |
25.08 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
1 | |||
execute | |
89.47% |
34 / 38 |
|
0.00% |
0 / 1 |
4.02 | |||
mustBePosted | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
isWriteMode | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getAllowedParams | |
100.00% |
24 / 24 |
|
100.00% |
1 / 1 |
1 | |||
needsToken | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getRbUser | |
75.00% |
3 / 4 |
|
0.00% |
0 / 1 |
2.06 | |||
getRbTitle | |
57.14% |
8 / 14 |
|
0.00% |
0 / 1 |
13.04 | |||
getExamplesMessages | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
2 | |||
getHelpUrls | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | /** |
3 | * Copyright © 2007 Roan Kattouw <roan.kattouw@gmail.com> |
4 | * |
5 | * This program is free software; you can redistribute it and/or modify |
6 | * it under the terms of the GNU General Public License as published by |
7 | * the Free Software Foundation; either version 2 of the License, or |
8 | * (at your option) any later version. |
9 | * |
10 | * This program is distributed in the hope that it will be useful, |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13 | * GNU General Public License for more details. |
14 | * |
15 | * You should have received a copy of the GNU General Public License along |
16 | * with this program; if not, write to the Free Software Foundation, Inc., |
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
18 | * http://www.gnu.org/copyleft/gpl.html |
19 | * |
20 | * @file |
21 | */ |
22 | |
23 | use MediaWiki\Deferred\DeferredUpdates; |
24 | use MediaWiki\MainConfigNames; |
25 | use MediaWiki\Page\RollbackPageFactory; |
26 | use MediaWiki\ParamValidator\TypeDef\UserDef; |
27 | use MediaWiki\Title\Title; |
28 | use MediaWiki\User\Options\UserOptionsLookup; |
29 | use MediaWiki\User\UserIdentity; |
30 | use MediaWiki\Watchlist\WatchlistManager; |
31 | use Wikimedia\ParamValidator\ParamValidator; |
32 | |
33 | /** |
34 | * @ingroup API |
35 | */ |
36 | class ApiRollback extends ApiBase { |
37 | |
38 | use ApiWatchlistTrait; |
39 | |
40 | private RollbackPageFactory $rollbackPageFactory; |
41 | |
42 | public function __construct( |
43 | ApiMain $mainModule, |
44 | $moduleName, |
45 | RollbackPageFactory $rollbackPageFactory, |
46 | WatchlistManager $watchlistManager, |
47 | UserOptionsLookup $userOptionsLookup |
48 | ) { |
49 | parent::__construct( $mainModule, $moduleName ); |
50 | $this->rollbackPageFactory = $rollbackPageFactory; |
51 | |
52 | // Variables needed in ApiWatchlistTrait trait |
53 | $this->watchlistExpiryEnabled = $this->getConfig()->get( MainConfigNames::WatchlistExpiry ); |
54 | $this->watchlistMaxDuration = |
55 | $this->getConfig()->get( MainConfigNames::WatchlistExpiryMaxDuration ); |
56 | $this->watchlistManager = $watchlistManager; |
57 | $this->userOptionsLookup = $userOptionsLookup; |
58 | } |
59 | |
60 | /** |
61 | * @var Title |
62 | */ |
63 | private $mTitleObj = null; |
64 | |
65 | /** |
66 | * @var UserIdentity |
67 | */ |
68 | private $mUser = null; |
69 | |
70 | public function execute() { |
71 | $this->useTransactionalTimeLimit(); |
72 | |
73 | $user = $this->getUser(); |
74 | $params = $this->extractRequestParams(); |
75 | |
76 | $titleObj = $this->getRbTitle( $params ); |
77 | |
78 | // If change tagging was requested, check that the user is allowed to tag, |
79 | // and the tags are valid. TODO: move inside rollback command? |
80 | if ( $params['tags'] ) { |
81 | $tagStatus = ChangeTags::canAddTagsAccompanyingChange( $params['tags'], $this->getAuthority() ); |
82 | if ( !$tagStatus->isOK() ) { |
83 | $this->dieStatus( $tagStatus ); |
84 | } |
85 | } |
86 | |
87 | // @TODO: remove this hack once rollback uses POST (T88044) |
88 | $fname = __METHOD__; |
89 | $trxLimits = $this->getConfig()->get( MainConfigNames::TrxProfilerLimits ); |
90 | $trxProfiler = Profiler::instance()->getTransactionProfiler(); |
91 | $trxProfiler->redefineExpectations( $trxLimits['POST'], $fname ); |
92 | DeferredUpdates::addCallableUpdate( static function () use ( $trxProfiler, $trxLimits, $fname ) { |
93 | $trxProfiler->redefineExpectations( $trxLimits['PostSend-POST'], $fname ); |
94 | } ); |
95 | |
96 | $rollbackResult = $this->rollbackPageFactory |
97 | ->newRollbackPage( $titleObj, $this->getAuthority(), $this->getRbUser( $params ) ) |
98 | ->setSummary( $params['summary'] ) |
99 | ->markAsBot( $params['markbot'] ) |
100 | ->setChangeTags( $params['tags'] ) |
101 | ->rollbackIfAllowed(); |
102 | |
103 | if ( !$rollbackResult->isGood() ) { |
104 | $this->dieStatus( $rollbackResult ); |
105 | } |
106 | |
107 | $watch = $params['watchlist'] ?? 'preferences'; |
108 | $watchlistExpiry = $this->getExpiryFromParams( $params ); |
109 | |
110 | // Watch pages |
111 | $this->setWatch( $watch, $titleObj, $user, 'watchrollback', $watchlistExpiry ); |
112 | |
113 | $details = $rollbackResult->getValue(); |
114 | $currentRevisionRecord = $details['current-revision-record']; |
115 | $targetRevisionRecord = $details['target-revision-record']; |
116 | |
117 | $info = [ |
118 | 'title' => $titleObj->getPrefixedText(), |
119 | 'pageid' => $currentRevisionRecord->getPageId(), |
120 | 'summary' => $details['summary'], |
121 | 'revid' => (int)$details['newid'], |
122 | // The revision being reverted (previously the current revision of the page) |
123 | 'old_revid' => $currentRevisionRecord->getID(), |
124 | // The revision being restored (the last revision before revision(s) by the reverted user) |
125 | 'last_revid' => $targetRevisionRecord->getID() |
126 | ]; |
127 | |
128 | $this->getResult()->addValue( null, $this->getModuleName(), $info ); |
129 | } |
130 | |
131 | public function mustBePosted() { |
132 | return true; |
133 | } |
134 | |
135 | public function isWriteMode() { |
136 | return true; |
137 | } |
138 | |
139 | public function getAllowedParams() { |
140 | $params = [ |
141 | 'title' => null, |
142 | 'pageid' => [ |
143 | ParamValidator::PARAM_TYPE => 'integer' |
144 | ], |
145 | 'tags' => [ |
146 | ParamValidator::PARAM_TYPE => 'tags', |
147 | ParamValidator::PARAM_ISMULTI => true, |
148 | ], |
149 | 'user' => [ |
150 | ParamValidator::PARAM_TYPE => 'user', |
151 | UserDef::PARAM_ALLOWED_USER_TYPES => [ 'name', 'ip', 'temp', 'id', 'interwiki' ], |
152 | UserDef::PARAM_RETURN_OBJECT => true, |
153 | ParamValidator::PARAM_REQUIRED => true |
154 | ], |
155 | 'summary' => '', |
156 | 'markbot' => false, |
157 | ]; |
158 | |
159 | // Params appear in the docs in the order they are defined, |
160 | // which is why this is here (we want it above the token param). |
161 | $params += $this->getWatchlistParams(); |
162 | |
163 | return $params + [ |
164 | 'token' => [ |
165 | // Standard definition automatically inserted |
166 | ApiBase::PARAM_HELP_MSG_APPEND => [ 'api-help-param-token-webui' ], |
167 | ], |
168 | ]; |
169 | } |
170 | |
171 | public function needsToken() { |
172 | return 'rollback'; |
173 | } |
174 | |
175 | /** |
176 | * @param array $params |
177 | * |
178 | * @return UserIdentity |
179 | */ |
180 | private function getRbUser( array $params ): UserIdentity { |
181 | if ( $this->mUser !== null ) { |
182 | return $this->mUser; |
183 | } |
184 | |
185 | $this->mUser = $params['user']; |
186 | |
187 | return $this->mUser; |
188 | } |
189 | |
190 | /** |
191 | * @param array $params |
192 | * |
193 | * @return Title |
194 | */ |
195 | private function getRbTitle( array $params ) { |
196 | if ( $this->mTitleObj !== null ) { |
197 | return $this->mTitleObj; |
198 | } |
199 | |
200 | $this->requireOnlyOneParameter( $params, 'title', 'pageid' ); |
201 | |
202 | if ( isset( $params['title'] ) ) { |
203 | $this->mTitleObj = Title::newFromText( $params['title'] ); |
204 | if ( !$this->mTitleObj || $this->mTitleObj->isExternal() ) { |
205 | $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $params['title'] ) ] ); |
206 | } |
207 | } elseif ( isset( $params['pageid'] ) ) { |
208 | $this->mTitleObj = Title::newFromID( $params['pageid'] ); |
209 | if ( !$this->mTitleObj ) { |
210 | $this->dieWithError( [ 'apierror-nosuchpageid', $params['pageid'] ] ); |
211 | } |
212 | } |
213 | |
214 | if ( !$this->mTitleObj->exists() ) { |
215 | $this->dieWithError( 'apierror-missingtitle' ); |
216 | } |
217 | |
218 | return $this->mTitleObj; |
219 | } |
220 | |
221 | protected function getExamplesMessages() { |
222 | $title = Title::newMainPage()->getPrefixedText(); |
223 | $mp = rawurlencode( $title ); |
224 | |
225 | return [ |
226 | "action=rollback&title={$mp}&user=Example&token=123ABC" => |
227 | 'apihelp-rollback-example-simple', |
228 | "action=rollback&title={$mp}&user=192.0.2.5&" . |
229 | 'token=123ABC&summary=Reverting%20vandalism&markbot=1' => |
230 | 'apihelp-rollback-example-summary', |
231 | ]; |
232 | } |
233 | |
234 | public function getHelpUrls() { |
235 | return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Rollback'; |
236 | } |
237 | } |