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