Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
90.62% |
87 / 96 |
|
63.64% |
7 / 11 |
CRAP | |
0.00% |
0 / 1 |
ApiContinuationManager | |
91.58% |
87 / 95 |
|
63.64% |
7 / 11 |
32.61 | |
0.00% |
0 / 1 |
__construct | |
96.15% |
25 / 26 |
|
0.00% |
0 / 1 |
7 | |||
getSource | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
isGeneratorDone | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getRunModules | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
addContinueParam | |
100.00% |
15 / 15 |
|
100.00% |
1 / 1 |
4 | |||
addGeneratorNonContinueParam | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
6 | |||
addGeneratorContinueParam | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
2 | |||
getRawContinuation | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getRawNonContinuation | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getContinuation | |
96.88% |
31 / 32 |
|
0.00% |
0 / 1 |
9 | |||
setContinuationIntoResult | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
3 |
1 | <?php |
2 | /** |
3 | * This program is free software; you can redistribute it and/or modify |
4 | * it under the terms of the GNU General Public License as published by |
5 | * the Free Software Foundation; either version 2 of the License, or |
6 | * (at your option) any later version. |
7 | * |
8 | * This program is distributed in the hope that it will be useful, |
9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
11 | * GNU General Public License for more details. |
12 | * |
13 | * You should have received a copy of the GNU General Public License along |
14 | * with this program; if not, write to the Free Software Foundation, Inc., |
15 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
16 | * http://www.gnu.org/copyleft/gpl.html |
17 | * |
18 | * @file |
19 | */ |
20 | |
21 | namespace MediaWiki\Api; |
22 | |
23 | use UnexpectedValueException; |
24 | |
25 | /** |
26 | * This manages continuation state. |
27 | * @since 1.25 this is no longer a subclass of ApiBase |
28 | * @ingroup API |
29 | */ |
30 | class ApiContinuationManager { |
31 | /** @var string */ |
32 | private $source; |
33 | |
34 | /** @var (ApiBase|false)[] */ |
35 | private $allModules = []; |
36 | /** @var string[] */ |
37 | private $generatedModules; |
38 | |
39 | /** @var array[] */ |
40 | private $continuationData = []; |
41 | /** @var array[] */ |
42 | private $generatorContinuationData = []; |
43 | /** @var array[] */ |
44 | private $generatorNonContinuationData = []; |
45 | |
46 | /** @var array */ |
47 | private $generatorParams = []; |
48 | /** @var bool */ |
49 | private $generatorDone = false; |
50 | |
51 | /** |
52 | * @param ApiBase $module Module starting the continuation |
53 | * @param ApiBase[] $allModules Contains ApiBase instances that will be executed |
54 | * @param string[] $generatedModules Names of modules that depend on the generator |
55 | * @throws ApiUsageException |
56 | */ |
57 | public function __construct( |
58 | ApiBase $module, array $allModules = [], array $generatedModules = [] |
59 | ) { |
60 | $this->source = get_class( $module ); |
61 | $request = $module->getRequest(); |
62 | |
63 | $this->generatedModules = $generatedModules |
64 | ? array_combine( $generatedModules, $generatedModules ) |
65 | : []; |
66 | |
67 | $skip = []; |
68 | $continue = $request->getVal( 'continue', '' ); |
69 | if ( $continue !== '' ) { |
70 | $continue = explode( '||', $continue ); |
71 | if ( count( $continue ) !== 2 ) { |
72 | throw ApiUsageException::newWithMessage( $module->getMain(), 'apierror-badcontinue' ); |
73 | } |
74 | $this->generatorDone = ( $continue[0] === '-' ); |
75 | $skip = explode( '|', $continue[1] ); |
76 | if ( !$this->generatorDone ) { |
77 | $params = explode( '|', $continue[0] ); |
78 | $this->generatorParams = array_intersect_key( |
79 | $request->getValues(), |
80 | array_fill_keys( $params, true ) |
81 | ); |
82 | } else { |
83 | // When the generator is complete, don't run any modules that |
84 | // depend on it. |
85 | $skip += $this->generatedModules; |
86 | } |
87 | } |
88 | |
89 | foreach ( $allModules as $module ) { |
90 | $name = $module->getModuleName(); |
91 | if ( in_array( $name, $skip, true ) ) { |
92 | $this->allModules[$name] = false; |
93 | // Prevent spurious "unused parameter" warnings |
94 | $module->extractRequestParams(); |
95 | } else { |
96 | $this->allModules[$name] = $module; |
97 | } |
98 | } |
99 | } |
100 | |
101 | /** |
102 | * Get the class that created this manager |
103 | * @return string |
104 | */ |
105 | public function getSource() { |
106 | return $this->source; |
107 | } |
108 | |
109 | /** |
110 | * @return bool |
111 | */ |
112 | public function isGeneratorDone() { |
113 | return $this->generatorDone; |
114 | } |
115 | |
116 | /** |
117 | * Get the list of modules that should actually be run |
118 | * @return ApiBase[] |
119 | */ |
120 | public function getRunModules() { |
121 | return array_values( array_filter( $this->allModules ) ); |
122 | } |
123 | |
124 | /** |
125 | * Set the continuation parameter for a module |
126 | * @param ApiBase $module |
127 | * @param string $paramName |
128 | * @param string|array $paramValue |
129 | * @throws UnexpectedValueException |
130 | */ |
131 | public function addContinueParam( ApiBase $module, $paramName, $paramValue ) { |
132 | $name = $module->getModuleName(); |
133 | if ( !isset( $this->allModules[$name] ) ) { |
134 | throw new UnexpectedValueException( |
135 | "Module '$name' called " . __METHOD__ . |
136 | ' but was not passed to ' . __CLASS__ . '::__construct' |
137 | ); |
138 | } |
139 | if ( !$this->allModules[$name] ) { |
140 | throw new UnexpectedValueException( |
141 | "Module '$name' was not supposed to have been executed, but " . |
142 | 'it was executed anyway' |
143 | ); |
144 | } |
145 | $paramName = $module->encodeParamName( $paramName ); |
146 | if ( is_array( $paramValue ) ) { |
147 | $paramValue = implode( '|', $paramValue ); |
148 | } |
149 | $this->continuationData[$name][$paramName] = $paramValue; |
150 | } |
151 | |
152 | /** |
153 | * Set the non-continuation parameter for the generator module |
154 | * |
155 | * In case the generator isn't going to be continued, this sets the fields |
156 | * to return. |
157 | * |
158 | * @since 1.28 |
159 | * @param ApiBase $module |
160 | * @param string $paramName |
161 | * @param string|array $paramValue |
162 | */ |
163 | public function addGeneratorNonContinueParam( ApiBase $module, $paramName, $paramValue ) { |
164 | $name = $module->getModuleName(); |
165 | $paramName = $module->encodeParamName( $paramName ); |
166 | if ( is_array( $paramValue ) ) { |
167 | $paramValue = implode( '|', $paramValue ); |
168 | } |
169 | $this->generatorNonContinuationData[$name][$paramName] = $paramValue; |
170 | } |
171 | |
172 | /** |
173 | * Set the continuation parameter for the generator module |
174 | * @param ApiBase $module |
175 | * @param string $paramName |
176 | * @param int|string|array $paramValue |
177 | */ |
178 | public function addGeneratorContinueParam( ApiBase $module, $paramName, $paramValue ) { |
179 | $name = $module->getModuleName(); |
180 | $paramName = $module->encodeParamName( $paramName ); |
181 | if ( is_array( $paramValue ) ) { |
182 | $paramValue = implode( '|', $paramValue ); |
183 | } |
184 | $this->generatorContinuationData[$name][$paramName] = $paramValue; |
185 | } |
186 | |
187 | /** |
188 | * Fetch raw continuation data |
189 | * @return array[] |
190 | */ |
191 | public function getRawContinuation() { |
192 | return array_merge_recursive( $this->continuationData, $this->generatorContinuationData ); |
193 | } |
194 | |
195 | /** |
196 | * Fetch raw non-continuation data |
197 | * @since 1.28 |
198 | * @return array[] |
199 | */ |
200 | public function getRawNonContinuation() { |
201 | return $this->generatorNonContinuationData; |
202 | } |
203 | |
204 | /** |
205 | * Fetch continuation result data |
206 | * @return array [ (array)$data, (bool)$batchcomplete ] |
207 | */ |
208 | public function getContinuation() { |
209 | $data = []; |
210 | $batchcomplete = false; |
211 | |
212 | $finishedModules = array_diff( |
213 | array_keys( $this->allModules ), |
214 | array_keys( $this->continuationData ) |
215 | ); |
216 | |
217 | // First, grab the non-generator-using continuation data |
218 | $continuationData = array_diff_key( $this->continuationData, $this->generatedModules ); |
219 | foreach ( $continuationData as $kvp ) { |
220 | $data += $kvp; |
221 | } |
222 | |
223 | // Next, handle the generator-using continuation data |
224 | $continuationData = array_intersect_key( $this->continuationData, $this->generatedModules ); |
225 | if ( $continuationData ) { |
226 | // Some modules are unfinished: include those params, and copy |
227 | // the generator params. |
228 | foreach ( $continuationData as $kvp ) { |
229 | $data += $kvp; |
230 | } |
231 | $generatorParams = []; |
232 | foreach ( $this->generatorNonContinuationData as $kvp ) { |
233 | $generatorParams += $kvp; |
234 | } |
235 | $generatorParams += $this->generatorParams; |
236 | // @phan-suppress-next-line PhanTypeInvalidLeftOperand False positive in phan |
237 | $data += $generatorParams; |
238 | $generatorKeys = implode( '|', array_keys( $generatorParams ) ); |
239 | } elseif ( $this->generatorContinuationData ) { |
240 | // All the generator-using modules are complete, but the |
241 | // generator isn't. Continue the generator and restart the |
242 | // generator-using modules |
243 | $generatorParams = []; |
244 | foreach ( $this->generatorContinuationData as $kvp ) { |
245 | $generatorParams += $kvp; |
246 | } |
247 | $data += $generatorParams; |
248 | $finishedModules = array_diff( $finishedModules, $this->generatedModules ); |
249 | $generatorKeys = implode( '|', array_keys( $generatorParams ) ); |
250 | $batchcomplete = true; |
251 | } else { |
252 | // Generator and prop modules are all done. Mark it so. |
253 | $generatorKeys = '-'; |
254 | $batchcomplete = true; |
255 | } |
256 | |
257 | // Set 'continue' if any continuation data is set or if the generator |
258 | // still needs to run |
259 | if ( $data || $generatorKeys !== '-' ) { |
260 | $data['continue'] = $generatorKeys . '||' . implode( '|', $finishedModules ); |
261 | } |
262 | |
263 | return [ $data, $batchcomplete ]; |
264 | } |
265 | |
266 | /** |
267 | * Store the continuation data into the result |
268 | * @param ApiResult $result |
269 | */ |
270 | public function setContinuationIntoResult( ApiResult $result ) { |
271 | [ $data, $batchcomplete ] = $this->getContinuation(); |
272 | if ( $data ) { |
273 | $result->addValue( null, 'continue', $data, |
274 | ApiResult::ADD_ON_TOP | ApiResult::NO_SIZE_CHECK ); |
275 | } |
276 | if ( $batchcomplete ) { |
277 | $result->addValue( null, 'batchcomplete', true, |
278 | ApiResult::ADD_ON_TOP | ApiResult::NO_SIZE_CHECK ); |
279 | } |
280 | } |
281 | } |
282 | |
283 | /** @deprecated class alias since 1.43 */ |
284 | class_alias( ApiContinuationManager::class, 'ApiContinuationManager' ); |