Merge "Revert "Use display name in category page subheadings if provided""
[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 UsageException
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 new UsageException(
61 'Invalid continue param. You should pass the original value returned by the previous query',
62 'badcontinue'
63 );
64 }
65 $this->generatorDone = ( $continue[0] === '-' );
66 $skip = explode( '|', $continue[1] );
67 if ( !$this->generatorDone ) {
68 $params = explode( '|', $continue[0] );
69 if ( $params ) {
70 $this->generatorParams = array_intersect_key(
71 $request->getValues(),
72 array_flip( $params )
73 );
74 }
75 } else {
76 // When the generator is complete, don't run any modules that
77 // depend on it.
78 $skip += $this->generatedModules;
79 }
80 }
81
82 foreach ( $allModules as $module ) {
83 $name = $module->getModuleName();
84 if ( in_array( $name, $skip, true ) ) {
85 $this->allModules[$name] = false;
86 // Prevent spurious "unused parameter" warnings
87 $module->extractRequestParams();
88 } else {
89 $this->allModules[$name] = $module;
90 }
91 }
92 }
93
94 /**
95 * Get the class that created this manager
96 * @return string
97 */
98 public function getSource() {
99 return $this->source;
100 }
101
102 /**
103 * Is the generator done?
104 * @return bool
105 */
106 public function isGeneratorDone() {
107 return $this->generatorDone;
108 }
109
110 /**
111 * Get the list of modules that should actually be run
112 * @return ApiBase[]
113 */
114 public function getRunModules() {
115 return array_values( array_filter( $this->allModules ) );
116 }
117
118 /**
119 * Set the continuation parameter for a module
120 * @param ApiBase $module
121 * @param string $paramName
122 * @param string|array $paramValue
123 * @throws UnexpectedValueException
124 */
125 public function addContinueParam( ApiBase $module, $paramName, $paramValue ) {
126 $name = $module->getModuleName();
127 if ( !isset( $this->allModules[$name] ) ) {
128 throw new UnexpectedValueException(
129 "Module '$name' called " . __METHOD__ .
130 ' but was not passed to ' . __CLASS__ . '::__construct'
131 );
132 }
133 if ( !$this->allModules[$name] ) {
134 throw new UnexpectedValueException(
135 "Module '$name' was not supposed to have been executed, but " .
136 'it was executed anyway'
137 );
138 }
139 $paramName = $module->encodeParamName( $paramName );
140 if ( is_array( $paramValue ) ) {
141 $paramValue = implode( '|', $paramValue );
142 }
143 $this->continuationData[$name][$paramName] = $paramValue;
144 }
145
146 /**
147 * Set the non-continuation parameter for the generator module
148 *
149 * In case the generator isn't going to be continued, this sets the fields
150 * to return.
151 *
152 * @since 1.28
153 * @param ApiBase $module
154 * @param string $paramName
155 * @param string|array $paramValue
156 */
157 public function addGeneratorNonContinueParam( ApiBase $module, $paramName, $paramValue ) {
158 $name = $module->getModuleName();
159 $paramName = $module->encodeParamName( $paramName );
160 if ( is_array( $paramValue ) ) {
161 $paramValue = implode( '|', $paramValue );
162 }
163 $this->generatorNonContinuationData[$name][$paramName] = $paramValue;
164 }
165
166 /**
167 * Set the continuation parameter for the generator module
168 * @param ApiBase $module
169 * @param string $paramName
170 * @param string|array $paramValue
171 */
172 public function addGeneratorContinueParam( ApiBase $module, $paramName, $paramValue ) {
173 $name = $module->getModuleName();
174 $paramName = $module->encodeParamName( $paramName );
175 if ( is_array( $paramValue ) ) {
176 $paramValue = implode( '|', $paramValue );
177 }
178 $this->generatorContinuationData[$name][$paramName] = $paramValue;
179 }
180
181 /**
182 * Fetch raw continuation data
183 * @return array
184 */
185 public function getRawContinuation() {
186 return array_merge_recursive( $this->continuationData, $this->generatorContinuationData );
187 }
188
189 /**
190 * Fetch raw non-continuation data
191 * @since 1.28
192 * @return array
193 */
194 public function getRawNonContinuation() {
195 return $this->generatorNonContinuationData;
196 }
197
198 /**
199 * Fetch continuation result data
200 * @return array [ (array)$data, (bool)$batchcomplete ]
201 */
202 public function getContinuation() {
203 $data = [];
204 $batchcomplete = false;
205
206 $finishedModules = array_diff(
207 array_keys( $this->allModules ),
208 array_keys( $this->continuationData )
209 );
210
211 // First, grab the non-generator-using continuation data
212 $continuationData = array_diff_key( $this->continuationData, $this->generatedModules );
213 foreach ( $continuationData as $module => $kvp ) {
214 $data += $kvp;
215 }
216
217 // Next, handle the generator-using continuation data
218 $continuationData = array_intersect_key( $this->continuationData, $this->generatedModules );
219 if ( $continuationData ) {
220 // Some modules are unfinished: include those params, and copy
221 // the generator params.
222 foreach ( $continuationData as $module => $kvp ) {
223 $data += $kvp;
224 }
225 $generatorParams = [];
226 foreach ( $this->generatorNonContinuationData as $kvp ) {
227 $generatorParams += $kvp;
228 }
229 $generatorParams += $this->generatorParams;
230 $data += $generatorParams;
231 $generatorKeys = implode( '|', array_keys( $generatorParams ) );
232 } elseif ( $this->generatorContinuationData ) {
233 // All the generator-using modules are complete, but the
234 // generator isn't. Continue the generator and restart the
235 // generator-using modules
236 $generatorParams = [];
237 foreach ( $this->generatorContinuationData as $kvp ) {
238 $generatorParams += $kvp;
239 }
240 $data += $generatorParams;
241 $finishedModules = array_diff( $finishedModules, $this->generatedModules );
242 $generatorKeys = implode( '|', array_keys( $generatorParams ) );
243 $batchcomplete = true;
244 } else {
245 // Generator and prop modules are all done. Mark it so.
246 $generatorKeys = '-';
247 $batchcomplete = true;
248 }
249
250 // Set 'continue' if any continuation data is set or if the generator
251 // still needs to run
252 if ( $data || $generatorKeys !== '-' ) {
253 $data['continue'] = $generatorKeys . '||' . implode( '|', $finishedModules );
254 }
255
256 return [ $data, $batchcomplete ];
257 }
258
259 /**
260 * Store the continuation data into the result
261 * @param ApiResult $result
262 */
263 public function setContinuationIntoResult( ApiResult $result ) {
264 list( $data, $batchcomplete ) = $this->getContinuation();
265 if ( $data ) {
266 $result->addValue( null, 'continue', $data,
267 ApiResult::ADD_ON_TOP | ApiResult::NO_SIZE_CHECK );
268 }
269 if ( $batchcomplete ) {
270 $result->addValue( null, 'batchcomplete', true,
271 ApiResult::ADD_ON_TOP | ApiResult::NO_SIZE_CHECK );
272 }
273 }
274 }