diff --git a/core/includes/batch.inc b/core/includes/batch.inc index 2640fc4..3a835e5 100644 --- a/core/includes/batch.inc +++ b/core/includes/batch.inc @@ -15,11 +15,6 @@ */ use Drupal\Component\Utility\SafeMarkup; -use Drupal\Component\Utility\Timer; -use Drupal\Component\Utility\UrlHelper; -use Drupal\Core\Batch\Percentage; -use Drupal\Core\Form\FormState; -use Drupal\Core\Url; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RedirectResponse; @@ -69,13 +64,15 @@ function _batch_page(Request $request) { } } + /** @var \Drupal\Core\Batch\BatchProcessorInterface */ + $batch_processor = \Drupal::service('batch.processor'); $op = $request->get('op', ''); switch ($op) { case 'start': case 'do_nojs': // Display the full progress page on startup and on each additional // non-JavaScript iteration. - $current_set = \Drupal::service('batch.processor')->currentSet(); + $current_set = $batch_processor->currentSet(); $build['#title'] = $current_set['title']; $build['content'] = _batch_progress_page(); break; @@ -87,7 +84,7 @@ function _batch_page(Request $request) { case 'finished': // \Drupal\Core\Batch\BatchProcessorInterface::finished() returns a // RedirectResponse. - return \Drupal::service('batch.processor')->finished(); + return $batch_processor->finished(); } return $build; @@ -97,11 +94,13 @@ function _batch_page(Request $request) { * Does one execution pass with JavaScript and returns progress to the browser. * * @see _batch_progress_page_js() - * @see \Drupal\Core\Batch\BatchProcessorInterface::process() + * @see \Drupal\Core\Batch\BatchProcessorInterface::processBatchSet() */ function _batch_do() { // Perform actual processing. - list($percentage, $message, $label) = \Drupal::service('batch.processor')->process(); + /** @var \Drupal\Core\Batch\BatchProcessorInterface */ + $batch_processor = \Drupal::service('batch.processor'); + list($percentage, $message, $label) = $batch_processor->process(); return new JsonResponse(array('status' => TRUE, 'percentage' => $percentage, 'message' => $message, 'label' => $label)); } @@ -109,12 +108,14 @@ function _batch_do() { /** * Outputs a batch processing page. * - * @see \Drupal\Core\Batch\BatchProcessorInterface::process() + * @see \Drupal\Core\Batch\BatchProcessorInterface::processBatchSet() */ function _batch_progress_page() { $batch = &batch_get(); - $current_set = \Drupal::service('batch.processor')->currentSet(); + /** @var \Drupal\Core\Batch\BatchProcessorInterface */ + $batch_processor = \Drupal::service('batch.processor'); + $current_set = $batch_processor->currentSet(); $new_op = 'do_nojs'; @@ -150,7 +151,7 @@ function _batch_progress_page() { print $fallback; // Perform actual processing. - list($percentage, $message, $label) = \Drupal::service('batch.processor')->process($batch); + list($percentage, $message, $label) = $batch_processor->process($batch); if ($percentage == 100) { $new_op = 'finished'; } diff --git a/core/includes/form.inc b/core/includes/form.inc index ba28053..8f381ae 100644 --- a/core/includes/form.inc +++ b/core/includes/form.inc @@ -795,102 +795,18 @@ function batch_set($batch_definition) { * A redirect response if the batch is progressive. No return value otherwise. */ function batch_process($redirect = NULL, Url $url = NULL, $redirect_callback = NULL) { - $batch =& batch_get(); - - if (isset($batch)) { - // Add process information - $process_info = array( - 'current_set' => 0, - 'progressive' => TRUE, - 'url' => isset($url) ? $url : Url::fromRoute('system.batch_page.html'), - 'source_url' => Url::fromRouteMatch(\Drupal::routeMatch()), - 'batch_redirect' => $redirect, - 'theme' => \Drupal::theme()->getActiveTheme()->getName(), - 'redirect_callback' => $redirect_callback, - ); - $batch += $process_info; - - // The batch is now completely built. Allow other modules to make changes - // to the batch so that it is easier to reuse batch processes in other - // environments. - \Drupal::moduleHandler()->alter('batch', $batch); - - // Assign an arbitrary id: don't rely on a serial column in the 'batch' - // table, since non-progressive batches skip database storage completely. - $batch['id'] = db_next_id(); - - // Move operations to a job queue. Non-progressive batches will use a - // memory-based queue. - foreach ($batch['sets'] as $key => $batch_set) { - _batch_populate_queue($batch, $key); - } - - // Initiate processing. - if ($batch['progressive']) { - // Now that we have a batch id, we can generate the redirection link in - // the generic error message. - /** @var \Drupal\Core\Url $batch_url */ - $batch_url = $batch['url']; - /** @var \Drupal\Core\Url $error_url */ - $error_url = clone $batch_url; - $query_options = $error_url->getOption('query'); - $query_options['id'] = $batch['id']; - $query_options['op'] = 'finished'; - $error_url->setOption('query', $query_options); - - $batch['error_message'] = t('Please continue to the error page', array('@error_url' => $error_url->toString(TRUE)->getGeneratedUrl())); - - // Clear the way for the redirection to the batch processing page, by - // saving and unsetting the 'destination', if there is any. - $request = \Drupal::request(); - if ($request->query->has('destination')) { - $batch['destination'] = $request->query->get('destination'); - $request->query->remove('destination'); - } - - // Store safe strings. - // @todo Ensure we are not storing an excessively large string list in: - // https://www.drupal.org/node/2295823 - $batch['safe_strings'] = SafeMarkup::getAll(); - - // Store the batch. - \Drupal::service('batch.storage')->create($batch); - - // Set the batch number in the session to guarantee that it will stay alive. - $_SESSION['batches'][$batch['id']] = TRUE; - - // Redirect for processing. - $query_options = $error_url->getOption('query'); - $query_options['op'] = 'start'; - $query_options['id'] = $batch['id']; - $batch_url->setOption('query', $query_options); - if (($function = $batch['redirect_callback']) && function_exists($function)) { - $function($batch_url->toString(), ['query' => $query_options]); - } - else { - return new RedirectResponse($batch_url->setAbsolute()->toString(TRUE)->getGeneratedUrl()); - } - } - else { - // Non-progressive execution: bypass the whole progressbar workflow - // and execute the batch in one pass. - \Drupal::service('batch.processor')->process(); - } - } + /** @var \Drupal\Core\Batch\BatchProcessorInterface */ + $batch_processor = \Drupal::service('batch.processor'); + return $batch_processor->process($redirect, $url, $redirect_callback); } /** * Retrieves the current batch. */ function &batch_get() { - // Not drupal_static(), because Batch API operates at a lower level than most - // use-cases for resetting static variables, and we specifically do not want a - // global drupal_static_reset() resetting the batch information. Functions - // that are part of the Batch API and need to reset the batch information may - // call batch_get() and manipulate the result by reference. Functions that are - // not part of the Batch API can also do this, but shouldn't. - static $batch = array(); - return $batch; + /** @var \Drupal\Core\Batch\BatchProcessorInterface $batch_processor */ + $batch_processor = \Drupal::service('batch.processor'); + return $batch_processor->getBatch(); } /** diff --git a/core/lib/Drupal/Core/Batch/BatchProcessor.php b/core/lib/Drupal/Core/Batch/BatchProcessor.php index 13b0b98..e1507ba 100644 --- a/core/lib/Drupal/Core/Batch/BatchProcessor.php +++ b/core/lib/Drupal/Core/Batch/BatchProcessor.php @@ -7,6 +7,7 @@ namespace Drupal\Core\Batch; +use Drupal\Component\Utility\SafeMarkup; use Drupal\Component\Utility\Timer; use Drupal\Component\Utility\UrlHelper; use Drupal\Core\Datetime\DateFormatter; @@ -97,9 +98,118 @@ public function __construct($root, BatchQueueHandlerInterface $batch_queue, Date } /** + * Processes the batch. + * + * This function is generally not needed in form submit handlers; + * Form API takes care of batches that were set during form submission. + * + * @param \Drupal\Core\Url|string $redirect + * (optional) Either path or Url object to redirect to when the batch has + * finished processing. Note that to simply force a batch to (conditionally) + * redirect to a custom location after it is finished processing but to + * otherwise allow the standard form API batch handling to occur, it is not + * necessary to call batch_process() and use this parameter. Instead, make + * the batch 'finished' callback return an instance of + * \Symfony\Component\HttpFoundation\RedirectResponse, which will be used + * automatically by the standard batch processing pipeline (and which takes + * precedence over this parameter). + * @param \Drupal\Core\Url $url + * (optional - should only be used for separate scripts like update.php) + * URL of the batch processing page. + * @param $redirect_callback + * (optional) Specify a function to be called to redirect to the progressive + * processing page. + * + * @return \Symfony\Component\HttpFoundation\RedirectResponse|null + * A redirect response if the batch is progressive. No return value otherwise. + */ + public function process($redirect = NULL, Url $url = NULL, $redirect_callback = NULL) { + if (isset($this->batch)) { + // Add process information + $process_info = array( + 'current_set' => 0, + 'progressive' => TRUE, + 'url' => isset($url) ? $url : Url::fromRoute('system.batch_page.html'), + 'source_url' => Url::fromRouteMatch(\Drupal::routeMatch() ), + 'batch_redirect' => $redirect, + 'theme' => \Drupal::theme()->getActiveTheme()->getName(), + 'redirect_callback' => $redirect_callback, + ); + $this->batch += $process_info; + + // The batch is now completely built. Allow other modules to make changes + // to the batch so that it is easier to reuse batch processes in other + // environments. + \Drupal::moduleHandler()->alter('batch', $this->batch); + + // Assign an arbitrary id: don't rely on a serial column in the 'batch' + // table, since non-progressive batches skip database storage completely. + $this->batch['id'] = db_next_id(); + + // Move operations to a job queue. Non-progressive batches will use a + // memory-based queue. + foreach ($this->batch['sets'] as $key => $batch_set) { + _batch_populate_queue($this->batch, $key); + } + + // Initiate processing. + if ($this->batch['progressive']) { + // Now that we have a batch id, we can generate the redirection link in + // the generic error message. + /** @var \Drupal\Core\Url $batch_url */ + $batch_url = $this->batch['url']; + /** @var \Drupal\Core\Url $error_url */ + $error_url = clone $batch_url; + $query_options = $error_url->getOption('query'); + $query_options['id'] = $this->batch['id']; + $query_options['op'] = 'finished'; + $error_url->setOption('query', $query_options); + + $this->batch['error_message'] = t('Please continue to the error page', array('@error_url' => $error_url->toString(TRUE)->getGeneratedUrl())); + + // Clear the way for the redirection to the batch processing page, by + // saving and unsetting the 'destination', if there is any. + $request = \Drupal::request(); + if ($request->query->has('destination')) { + $this->batch['destination'] = $request->query->get('destination'); + $request->query->remove('destination'); + } + + // Store safe strings. + // @todo Ensure we are not storing an excessively large string list in: + // https://www.drupal.org/node/2295823 + $this->batch['safe_strings'] = SafeMarkup::getAll(); + + // Store the batch. + $this->batchStorage->create($this->batch); + + // Set the batch number in the session to guarantee that it will stay alive. + $_SESSION['batches'][$this->batch['id']] = TRUE; + + // Redirect for processing. + $query_options = $error_url->getOption('query'); + $query_options['op'] = 'start'; + $query_options['id'] = $this->batch['id']; + $batch_url->setOption('query', $query_options); + if (($function = $this->batch['redirect_callback']) && function_exists($function)) { + $function($batch_url->toString(), ['query' => $query_options]); + } + else { + return new RedirectResponse($batch_url->setAbsolute()->toString(TRUE)->getGeneratedUrl()); + } + } + else { + // Non-progressive execution: bypass the whole progressbar workflow + // and execute the batch in one pass. + $this->processBatchSet(); + } + } + } + + /** * {@inheritdoc} */ - public function process() { + public function processBatchSet() { $current_set = $this->currentSet(); // Indicate that this batch set needs to be initialized. $set_changed = TRUE; @@ -235,6 +345,13 @@ public function currentSet() { } /** + * @return array + */ + public function getBatch() { + return $this->batch; + } + + /** * {@inheritdoc} */ public function nextSet() { diff --git a/core/lib/Drupal/Core/Batch/BatchProcessorInterface.php b/core/lib/Drupal/Core/Batch/BatchProcessorInterface.php index 4ccb995..145c30a 100644 --- a/core/lib/Drupal/Core/Batch/BatchProcessorInterface.php +++ b/core/lib/Drupal/Core/Batch/BatchProcessorInterface.php @@ -6,7 +6,6 @@ */ namespace Drupal\Core\Batch; -use Symfony\Component\HttpFoundation\RedirectResponse; /** * Processes batch requests. @@ -24,7 +23,7 @@ * @return array * An array containing a completion value (in percent) and a status message. */ - public function process(); + public function processBatchSet(); /** * Returns the batch set being currently processed. @@ -57,4 +56,9 @@ public function nextSet(); */ public function finished(); + /** + * @return array + */ + public function &getBatch(); + } diff --git a/core/lib/Drupal/Core/Batch/Percentage.php b/core/lib/Drupal/Core/Batch/Percentage.php index 0eb30e2..9a5d234 100644 --- a/core/lib/Drupal/Core/Batch/Percentage.php +++ b/core/lib/Drupal/Core/Batch/Percentage.php @@ -29,7 +29,7 @@ class Percentage { * until we are finished, but we also never print more decimal places than * are meaningful. * - * @see \Drupal\Core\Batch\BatchProcessorInterface::process() + * @see \Drupal\Core\Batch\BatchProcessorInterface::processBatchSet() */ public static function format($total, $current) { if (!$total || $total == $current) { diff --git a/core/tests/Drupal/Tests/Core/Batch/BatchProcessorTest.php b/core/tests/Drupal/Tests/Core/Batch/BatchProcessorTest.php index 3dbcd64..c4af0be 100644 --- a/core/tests/Drupal/Tests/Core/Batch/BatchProcessorTest.php +++ b/core/tests/Drupal/Tests/Core/Batch/BatchProcessorTest.php @@ -71,4 +71,13 @@ protected function setupBatchProcessor() { return new BatchProcessor('core', $this->batchQueue->reveal(), $this->dateFormatter->reveal(), $this->formSubmitter->reveal(), $this->batchStorage->reveal(), $this->requestStack); } + /** + * @covers ::getBatch + */ + public function testGetEmptyBatch() { + $batch = $this->setupBatchProcessor(); + + $this->assertEquals(['sets' => []], $batch->getBatch()); + } + }