ApiContinuationManager.php 8.1 KB

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