User::isSafeToLoad() should return false if MW_NO_SESSION
[mediawiki.git] / includes / api / ApiContinuationManager.php
blob25407bfe96492fc9bfb97326ed8ef83a523601ff
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.
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
18 * @file
21 /**
22 * This manages continuation state.
23 * @since 1.25 this is no longer a subclass of ApiBase
24 * @ingroup API
26 class ApiContinuationManager {
27 private $source;
29 private $allModules = [];
30 private $generatedModules = [];
32 private $continuationData = [];
33 private $generatorContinuationData = [];
35 private $generatorParams = [];
36 private $generatorDone = false;
38 /**
39 * @param ApiBase $module Module starting the continuation
40 * @param ApiBase[] $allModules Contains ApiBase instances that will be executed
41 * @param array $generatedModules Names of modules that depend on the generator
42 * @throws UsageException
44 public function __construct(
45 ApiBase $module, array $allModules = [], array $generatedModules = []
46 ) {
47 $this->source = get_class( $module );
48 $request = $module->getRequest();
50 $this->generatedModules = $generatedModules
51 ? array_combine( $generatedModules, $generatedModules )
52 : [];
54 $skip = [];
55 $continue = $request->getVal( 'continue', '' );
56 if ( $continue !== '' ) {
57 $continue = explode( '||', $continue );
58 if ( count( $continue ) !== 2 ) {
59 throw new UsageException(
60 'Invalid continue param. You should pass the original value returned by the previous query',
61 'badcontinue'
64 $this->generatorDone = ( $continue[0] === '-' );
65 $skip = explode( '|', $continue[1] );
66 if ( !$this->generatorDone ) {
67 $params = explode( '|', $continue[0] );
68 if ( $params ) {
69 $this->generatorParams = array_intersect_key(
70 $request->getValues(),
71 array_flip( $params )
74 } else {
75 // When the generator is complete, don't run any modules that
76 // depend on it.
77 $skip += $this->generatedModules;
81 foreach ( $allModules as $module ) {
82 $name = $module->getModuleName();
83 if ( in_array( $name, $skip, true ) ) {
84 $this->allModules[$name] = false;
85 // Prevent spurious "unused parameter" warnings
86 $module->extractRequestParams();
87 } else {
88 $this->allModules[$name] = $module;
93 /**
94 * Get the class that created this manager
95 * @return string
97 public function getSource() {
98 return $this->source;
102 * Is the generator done?
103 * @return bool
105 public function isGeneratorDone() {
106 return $this->generatorDone;
110 * Get the list of modules that should actually be run
111 * @return ApiBase[]
113 public function getRunModules() {
114 return array_values( array_filter( $this->allModules ) );
118 * Set the continuation parameter for a module
119 * @param ApiBase $module
120 * @param string $paramName
121 * @param string|array $paramValue
122 * @throws UnexpectedValueException
124 public function addContinueParam( ApiBase $module, $paramName, $paramValue ) {
125 $name = $module->getModuleName();
126 if ( !isset( $this->allModules[$name] ) ) {
127 throw new UnexpectedValueException(
128 "Module '$name' called " . __METHOD__ .
129 ' but was not passed to ' . __CLASS__ . '::__construct'
132 if ( !$this->allModules[$name] ) {
133 throw new UnexpectedValueException(
134 "Module '$name' was not supposed to have been executed, but " .
135 'it was executed anyway'
138 $paramName = $module->encodeParamName( $paramName );
139 if ( is_array( $paramValue ) ) {
140 $paramValue = join( '|', $paramValue );
142 $this->continuationData[$name][$paramName] = $paramValue;
146 * Set the continuation parameter for the generator module
147 * @param ApiBase $module
148 * @param string $paramName
149 * @param string|array $paramValue
151 public function addGeneratorContinueParam( ApiBase $module, $paramName, $paramValue ) {
152 $name = $module->getModuleName();
153 $paramName = $module->encodeParamName( $paramName );
154 if ( is_array( $paramValue ) ) {
155 $paramValue = join( '|', $paramValue );
157 $this->generatorContinuationData[$name][$paramName] = $paramValue;
161 * Fetch raw continuation data
162 * @return array
164 public function getRawContinuation() {
165 return array_merge_recursive( $this->continuationData, $this->generatorContinuationData );
169 * Fetch continuation result data
170 * @return array Array( (array)$data, (bool)$batchcomplete )
172 public function getContinuation() {
173 $data = [];
174 $batchcomplete = false;
176 $finishedModules = array_diff(
177 array_keys( $this->allModules ),
178 array_keys( $this->continuationData )
181 // First, grab the non-generator-using continuation data
182 $continuationData = array_diff_key( $this->continuationData, $this->generatedModules );
183 foreach ( $continuationData as $module => $kvp ) {
184 $data += $kvp;
187 // Next, handle the generator-using continuation data
188 $continuationData = array_intersect_key( $this->continuationData, $this->generatedModules );
189 if ( $continuationData ) {
190 // Some modules are unfinished: include those params, and copy
191 // the generator params.
192 foreach ( $continuationData as $module => $kvp ) {
193 $data += $kvp;
195 $data += $this->generatorParams;
196 $generatorKeys = join( '|', array_keys( $this->generatorParams ) );
197 } elseif ( $this->generatorContinuationData ) {
198 // All the generator-using modules are complete, but the
199 // generator isn't. Continue the generator and restart the
200 // generator-using modules
201 $generatorParams = [];
202 foreach ( $this->generatorContinuationData as $kvp ) {
203 $generatorParams += $kvp;
205 $data += $generatorParams;
206 $finishedModules = array_diff( $finishedModules, $this->generatedModules );
207 $generatorKeys = join( '|', array_keys( $generatorParams ) );
208 $batchcomplete = true;
209 } else {
210 // Generator and prop modules are all done. Mark it so.
211 $generatorKeys = '-';
212 $batchcomplete = true;
215 // Set 'continue' if any continuation data is set or if the generator
216 // still needs to run
217 if ( $data || $generatorKeys !== '-' ) {
218 $data['continue'] = $generatorKeys . '||' . join( '|', $finishedModules );
221 return [ $data, $batchcomplete ];
225 * Store the continuation data into the result
226 * @param ApiResult $result
228 public function setContinuationIntoResult( ApiResult $result ) {
229 list( $data, $batchcomplete ) = $this->getContinuation();
230 if ( $data ) {
231 $result->addValue( null, 'continue', $data,
232 ApiResult::ADD_ON_TOP | ApiResult::NO_SIZE_CHECK );
234 if ( $batchcomplete ) {
235 $result->addValue( null, 'batchcomplete', true,
236 ApiResult::ADD_ON_TOP | ApiResult::NO_SIZE_CHECK );