Merge "mw.widget.DateInputWidget: Add range validation"
[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 = array();
30 private $generatedModules = array();
31
32 private $continuationData = array();
33 private $generatorContinuationData = array();
34
35 private $generatorParams = array();
36 private $generatorDone = false;
37
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 */
43 public function __construct(
44 ApiBase $module, array $allModules = array(), array $generatedModules = array()
45 ) {
46 $this->source = get_class( $module );
47 $request = $module->getRequest();
48
49 $this->generatedModules = $generatedModules
50 ? array_combine( $generatedModules, $generatedModules )
51 : array();
52
53 $skip = array();
54 $continue = $request->getVal( 'continue', '' );
55 if ( $continue !== '' ) {
56 $continue = explode( '||', $continue );
57 if ( count( $continue ) !== 2 ) {
58 throw new UsageException(
59 'Invalid continue param. You should pass the original value returned by the previous query',
60 'badcontinue'
61 );
62 }
63 $this->generatorDone = ( $continue[0] === '-' );
64 $skip = explode( '|', $continue[1] );
65 if ( !$this->generatorDone ) {
66 $params = explode( '|', $continue[0] );
67 if ( $params ) {
68 $this->generatorParams = array_intersect_key(
69 $request->getValues(),
70 array_flip( $params )
71 );
72 }
73 } else {
74 // When the generator is complete, don't run any modules that
75 // depend on it.
76 $skip += $this->generatedModules;
77 }
78 }
79
80 foreach ( $allModules as $module ) {
81 $name = $module->getModuleName();
82 if ( in_array( $name, $skip, true ) ) {
83 $this->allModules[$name] = false;
84 // Prevent spurious "unused parameter" warnings
85 $module->extractRequestParams();
86 } else {
87 $this->allModules[$name] = $module;
88 }
89 }
90 }
91
92 /**
93 * Get the class that created this manager
94 * @return string
95 */
96 public function getSource() {
97 return $this->source;
98 }
99
100 /**
101 * Is the generator done?
102 * @return bool
103 */
104 public function isGeneratorDone() {
105 return $this->generatorDone;
106 }
107
108 /**
109 * Get the list of modules that should actually be run
110 * @return ApiBase[]
111 */
112 public function getRunModules() {
113 return array_values( array_filter( $this->allModules ) );
114 }
115
116 /**
117 * Set the continuation parameter for a module
118 * @param ApiBase $module
119 * @param string $paramName
120 * @param string|array $paramValue
121 * @throws UnexpectedValueException
122 */
123 public function addContinueParam( ApiBase $module, $paramName, $paramValue ) {
124 $name = $module->getModuleName();
125 if ( !isset( $this->allModules[$name] ) ) {
126 throw new UnexpectedValueException(
127 "Module '$name' called " . __METHOD__ .
128 ' but was not passed to ' . __CLASS__ . '::__construct'
129 );
130 }
131 if ( !$this->allModules[$name] ) {
132 throw new UnexpectedValueException(
133 "Module '$name' was not supposed to have been executed, but " .
134 'it was executed anyway'
135 );
136 }
137 $paramName = $module->encodeParamName( $paramName );
138 if ( is_array( $paramValue ) ) {
139 $paramValue = join( '|', $paramValue );
140 }
141 $this->continuationData[$name][$paramName] = $paramValue;
142 }
143
144 /**
145 * Set the continuation parameter for the generator module
146 * @param ApiBase $module
147 * @param string $paramName
148 * @param string|array $paramValue
149 */
150 public function addGeneratorContinueParam( ApiBase $module, $paramName, $paramValue ) {
151 $name = $module->getModuleName();
152 $paramName = $module->encodeParamName( $paramName );
153 if ( is_array( $paramValue ) ) {
154 $paramValue = join( '|', $paramValue );
155 }
156 $this->generatorContinuationData[$name][$paramName] = $paramValue;
157 }
158
159 /**
160 * Fetch raw continuation data
161 * @return array
162 */
163 public function getRawContinuation() {
164 return array_merge_recursive( $this->continuationData, $this->generatorContinuationData );
165 }
166
167 /**
168 * Fetch continuation result data
169 * @return array Array( (array)$data, (bool)$batchcomplete )
170 */
171 public function getContinuation() {
172 $data = array();
173 $batchcomplete = false;
174
175 $finishedModules = array_diff(
176 array_keys( $this->allModules ),
177 array_keys( $this->continuationData )
178 );
179
180 // First, grab the non-generator-using continuation data
181 $continuationData = array_diff_key( $this->continuationData, $this->generatedModules );
182 foreach ( $continuationData as $module => $kvp ) {
183 $data += $kvp;
184 }
185
186 // Next, handle the generator-using continuation data
187 $continuationData = array_intersect_key( $this->continuationData, $this->generatedModules );
188 if ( $continuationData ) {
189 // Some modules are unfinished: include those params, and copy
190 // the generator params.
191 foreach ( $continuationData as $module => $kvp ) {
192 $data += $kvp;
193 }
194 $data += $this->generatorParams;
195 $generatorKeys = join( '|', array_keys( $this->generatorParams ) );
196 } elseif ( $this->generatorContinuationData ) {
197 // All the generator-using modules are complete, but the
198 // generator isn't. Continue the generator and restart the
199 // generator-using modules
200 $generatorParams = array();
201 foreach ( $this->generatorContinuationData as $kvp ) {
202 $generatorParams += $kvp;
203 }
204 $data += $generatorParams;
205 $finishedModules = array_diff( $finishedModules, $this->generatedModules );
206 $generatorKeys = join( '|', array_keys( $generatorParams ) );
207 $batchcomplete = true;
208 } else {
209 // Generator and prop modules are all done. Mark it so.
210 $generatorKeys = '-';
211 $batchcomplete = true;
212 }
213
214 // Set 'continue' if any continuation data is set or if the generator
215 // still needs to run
216 if ( $data || $generatorKeys !== '-' ) {
217 $data['continue'] = $generatorKeys . '||' . join( '|', $finishedModules );
218 }
219
220 return array( $data, $batchcomplete );
221 }
222
223 /**
224 * Store the continuation data into the result
225 * @param ApiResult $result
226 */
227 public function setContinuationIntoResult( ApiResult $result ) {
228 list( $data, $batchcomplete ) = $this->getContinuation();
229 if ( $data ) {
230 $result->addValue( null, 'continue', $data,
231 ApiResult::ADD_ON_TOP | ApiResult::NO_SIZE_CHECK );
232 }
233 if ( $batchcomplete ) {
234 $result->addValue( null, 'batchcomplete', true,
235 ApiResult::ADD_ON_TOP | ApiResult::NO_SIZE_CHECK );
236 }
237 }
238 }