Merge "Added a separate error message for mkdir failures"
[lhc/web/wiklou.git] / includes / api / ApiContinuationManager.php
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 private $source;
28
29 private $allModules = [];
30 private $generatedModules = [];
31
32 private $continuationData = [];
33 private $generatorContinuationData = [];
34 private $generatorNonContinuationData = [];
35
36 private $generatorParams = [];
37 private $generatorDone = false;
38
39 /**
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
44 */
45 public function __construct(
46 ApiBase $module, array $allModules = [], array $generatedModules = []
47 ) {
48 $this->source = get_class( $module );
49 $request = $module->getRequest();
50
51 $this->generatedModules = $generatedModules
52 ? array_combine( $generatedModules, $generatedModules )
53 : [];
54
55 $skip = [];
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' );
61 }
62 $this->generatorDone = ( $continue[0] === '-' );
63 $skip = explode( '|', $continue[1] );
64 if ( !$this->generatorDone ) {
65 $params = explode( '|', $continue[0] );
66 if ( $params ) {
67 $this->generatorParams = array_intersect_key(
68 $request->getValues(),
69 array_flip( $params )
70 );
71 }
72 } else {
73 // When the generator is complete, don't run any modules that
74 // depend on it.
75 $skip += $this->generatedModules;
76 }
77 }
78
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();
85 } else {
86 $this->allModules[$name] = $module;
87 }
88 }
89 }
90
91 /**
92 * Get the class that created this manager
93 * @return string
94 */
95 public function getSource() {
96 return $this->source;
97 }
98
99 /**
100 * Is the generator done?
101 * @return bool
102 */
103 public function isGeneratorDone() {
104 return $this->generatorDone;
105 }
106
107 /**
108 * Get the list of modules that should actually be run
109 * @return ApiBase[]
110 */
111 public function getRunModules() {
112 return array_values( array_filter( $this->allModules ) );
113 }
114
115 /**
116 * Set the continuation parameter for a module
117 * @param ApiBase $module
118 * @param string $paramName
119 * @param string|array $paramValue
120 * @throws UnexpectedValueException
121 */
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'
128 );
129 }
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'
134 );
135 }
136 $paramName = $module->encodeParamName( $paramName );
137 if ( is_array( $paramValue ) ) {
138 $paramValue = implode( '|', $paramValue );
139 }
140 $this->continuationData[$name][$paramName] = $paramValue;
141 }
142
143 /**
144 * Set the non-continuation parameter for the generator module
145 *
146 * In case the generator isn't going to be continued, this sets the fields
147 * to return.
148 *
149 * @since 1.28
150 * @param ApiBase $module
151 * @param string $paramName
152 * @param string|array $paramValue
153 */
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 );
159 }
160 $this->generatorNonContinuationData[$name][$paramName] = $paramValue;
161 }
162
163 /**
164 * Set the continuation parameter for the generator module
165 * @param ApiBase $module
166 * @param string $paramName
167 * @param string|array $paramValue
168 */
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 );
174 }
175 $this->generatorContinuationData[$name][$paramName] = $paramValue;
176 }
177
178 /**
179 * Fetch raw continuation data
180 * @return array
181 */
182 public function getRawContinuation() {
183 return array_merge_recursive( $this->continuationData, $this->generatorContinuationData );
184 }
185
186 /**
187 * Fetch raw non-continuation data
188 * @since 1.28
189 * @return array
190 */
191 public function getRawNonContinuation() {
192 return $this->generatorNonContinuationData;
193 }
194
195 /**
196 * Fetch continuation result data
197 * @return array [ (array)$data, (bool)$batchcomplete ]
198 */
199 public function getContinuation() {
200 $data = [];
201 $batchcomplete = false;
202
203 $finishedModules = array_diff(
204 array_keys( $this->allModules ),
205 array_keys( $this->continuationData )
206 );
207
208 // First, grab the non-generator-using continuation data
209 $continuationData = array_diff_key( $this->continuationData, $this->generatedModules );
210 foreach ( $continuationData as $module => $kvp ) {
211 $data += $kvp;
212 }
213
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 ) {
220 $data += $kvp;
221 }
222 $generatorParams = [];
223 foreach ( $this->generatorNonContinuationData as $kvp ) {
224 $generatorParams += $kvp;
225 }
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;
236 }
237 $data += $generatorParams;
238 $finishedModules = array_diff( $finishedModules, $this->generatedModules );
239 $generatorKeys = implode( '|', array_keys( $generatorParams ) );
240 $batchcomplete = true;
241 } else {
242 // Generator and prop modules are all done. Mark it so.
243 $generatorKeys = '-';
244 $batchcomplete = true;
245 }
246
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 );
251 }
252
253 return [ $data, $batchcomplete ];
254 }
255
256 /**
257 * Store the continuation data into the result
258 * @param ApiResult $result
259 */
260 public function setContinuationIntoResult( ApiResult $result ) {
261 list( $data, $batchcomplete ) = $this->getContinuation();
262 if ( $data ) {
263 $result->addValue( null, 'continue', $data,
264 ApiResult::ADD_ON_TOP | ApiResult::NO_SIZE_CHECK );
265 }
266 if ( $batchcomplete ) {
267 $result->addValue( null, 'batchcomplete', true,
268 ApiResult::ADD_ON_TOP | ApiResult::NO_SIZE_CHECK );
269 }
270 }
271 }