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.
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.
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
22 * This manages continuation state.
23 * @since 1.25 this is no longer a subclass of ApiBase
26 class ApiContinuationManager
{
29 private $allModules = [];
30 private $generatedModules = [];
32 private $continuationData = [];
33 private $generatorContinuationData = [];
34 private $generatorNonContinuationData = [];
36 private $generatorParams = [];
37 private $generatorDone = false;
40 * @param ApiBase $module Module starting the continuation
41 * @param ApiBase[] $allModules Contains ApiBase instances that will be executed
42 * @param array $generatedModules Names of modules that depend on the generator
43 * @throws ApiUsageException
45 public function __construct(
46 ApiBase
$module, array $allModules = [], array $generatedModules = []
48 $this->source
= get_class( $module );
49 $request = $module->getRequest();
51 $this->generatedModules
= $generatedModules
52 ?
array_combine( $generatedModules, $generatedModules )
56 $continue = $request->getVal( 'continue', '' );
57 if ( $continue !== '' ) {
58 $continue = explode( '||', $continue );
59 if ( count( $continue ) !== 2 ) {
60 throw ApiUsageException
::newWithMessage( $module->getMain(), 'apierror-badcontinue' );
62 $this->generatorDone
= ( $continue[0] === '-' );
63 $skip = explode( '|', $continue[1] );
64 if ( !$this->generatorDone
) {
65 $params = explode( '|', $continue[0] );
67 $this->generatorParams
= array_intersect_key(
68 $request->getValues(),
73 // When the generator is complete, don't run any modules that
75 $skip +
= $this->generatedModules
;
79 foreach ( $allModules as $module ) {
80 $name = $module->getModuleName();
81 if ( in_array( $name, $skip, true ) ) {
82 $this->allModules
[$name] = false;
83 // Prevent spurious "unused parameter" warnings
84 $module->extractRequestParams();
86 $this->allModules
[$name] = $module;
92 * Get the class that created this manager
95 public function getSource() {
100 * Is the generator done?
103 public function isGeneratorDone() {
104 return $this->generatorDone
;
108 * Get the list of modules that should actually be run
111 public function getRunModules() {
112 return array_values( array_filter( $this->allModules
) );
116 * Set the continuation parameter for a module
117 * @param ApiBase $module
118 * @param string $paramName
119 * @param string|array $paramValue
120 * @throws UnexpectedValueException
122 public function addContinueParam( ApiBase
$module, $paramName, $paramValue ) {
123 $name = $module->getModuleName();
124 if ( !isset( $this->allModules
[$name] ) ) {
125 throw new UnexpectedValueException(
126 "Module '$name' called " . __METHOD__
.
127 ' but was not passed to ' . __CLASS__
. '::__construct'
130 if ( !$this->allModules
[$name] ) {
131 throw new UnexpectedValueException(
132 "Module '$name' was not supposed to have been executed, but " .
133 'it was executed anyway'
136 $paramName = $module->encodeParamName( $paramName );
137 if ( is_array( $paramValue ) ) {
138 $paramValue = implode( '|', $paramValue );
140 $this->continuationData
[$name][$paramName] = $paramValue;
144 * Set the non-continuation parameter for the generator module
146 * In case the generator isn't going to be continued, this sets the fields
150 * @param ApiBase $module
151 * @param string $paramName
152 * @param string|array $paramValue
154 public function addGeneratorNonContinueParam( ApiBase
$module, $paramName, $paramValue ) {
155 $name = $module->getModuleName();
156 $paramName = $module->encodeParamName( $paramName );
157 if ( is_array( $paramValue ) ) {
158 $paramValue = implode( '|', $paramValue );
160 $this->generatorNonContinuationData
[$name][$paramName] = $paramValue;
164 * Set the continuation parameter for the generator module
165 * @param ApiBase $module
166 * @param string $paramName
167 * @param string|array $paramValue
169 public function addGeneratorContinueParam( ApiBase
$module, $paramName, $paramValue ) {
170 $name = $module->getModuleName();
171 $paramName = $module->encodeParamName( $paramName );
172 if ( is_array( $paramValue ) ) {
173 $paramValue = implode( '|', $paramValue );
175 $this->generatorContinuationData
[$name][$paramName] = $paramValue;
179 * Fetch raw continuation data
182 public function getRawContinuation() {
183 return array_merge_recursive( $this->continuationData
, $this->generatorContinuationData
);
187 * Fetch raw non-continuation data
191 public function getRawNonContinuation() {
192 return $this->generatorNonContinuationData
;
196 * Fetch continuation result data
197 * @return array [ (array)$data, (bool)$batchcomplete ]
199 public function getContinuation() {
201 $batchcomplete = false;
203 $finishedModules = array_diff(
204 array_keys( $this->allModules
),
205 array_keys( $this->continuationData
)
208 // First, grab the non-generator-using continuation data
209 $continuationData = array_diff_key( $this->continuationData
, $this->generatedModules
);
210 foreach ( $continuationData as $module => $kvp ) {
214 // Next, handle the generator-using continuation data
215 $continuationData = array_intersect_key( $this->continuationData
, $this->generatedModules
);
216 if ( $continuationData ) {
217 // Some modules are unfinished: include those params, and copy
218 // the generator params.
219 foreach ( $continuationData as $module => $kvp ) {
222 $generatorParams = [];
223 foreach ( $this->generatorNonContinuationData
as $kvp ) {
224 $generatorParams +
= $kvp;
226 $generatorParams +
= $this->generatorParams
;
227 $data +
= $generatorParams;
228 $generatorKeys = implode( '|', array_keys( $generatorParams ) );
229 } elseif ( $this->generatorContinuationData
) {
230 // All the generator-using modules are complete, but the
231 // generator isn't. Continue the generator and restart the
232 // generator-using modules
233 $generatorParams = [];
234 foreach ( $this->generatorContinuationData
as $kvp ) {
235 $generatorParams +
= $kvp;
237 $data +
= $generatorParams;
238 $finishedModules = array_diff( $finishedModules, $this->generatedModules
);
239 $generatorKeys = implode( '|', array_keys( $generatorParams ) );
240 $batchcomplete = true;
242 // Generator and prop modules are all done. Mark it so.
243 $generatorKeys = '-';
244 $batchcomplete = true;
247 // Set 'continue' if any continuation data is set or if the generator
248 // still needs to run
249 if ( $data ||
$generatorKeys !== '-' ) {
250 $data['continue'] = $generatorKeys . '||' . implode( '|', $finishedModules );
253 return [ $data, $batchcomplete ];
257 * Store the continuation data into the result
258 * @param ApiResult $result
260 public function setContinuationIntoResult( ApiResult
$result ) {
261 list( $data, $batchcomplete ) = $this->getContinuation();
263 $result->addValue( null, 'continue', $data,
264 ApiResult
::ADD_ON_TOP | ApiResult
::NO_SIZE_CHECK
);
266 if ( $batchcomplete ) {
267 $result->addValue( null, 'batchcomplete', true,
268 ApiResult
::ADD_ON_TOP | ApiResult
::NO_SIZE_CHECK
);