diff --git a/core/includes/form.inc b/core/includes/form.inc index c8d9b14..4382723 100644 --- a/core/includes/form.inc +++ b/core/includes/form.inc @@ -7,6 +7,7 @@ use Drupal\Component\Utility\UrlHelper; use Drupal\Core\Batch\Batch; +use Drupal\Core\Batch\BatchQueueController; use Drupal\Core\Render\Element; use Drupal\Core\Render\Element\RenderElement; use Drupal\Core\Template\Attribute; @@ -752,7 +753,7 @@ function batch_set($batch_definition) { * an existing Batch object. */ function batch_process($redirect = NULL, Url $url = NULL, $redirect_callback = NULL) { - return Batch::process($redirect, $url, $redirect_callback); + return BatchQueueController::process($redirect, $url, $redirect_callback); } /** @@ -765,41 +766,7 @@ function &batch_get() { // 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. - return Batch::getBatches(); -} - -/** - * Populates a job queue with the operations of a batch set. - * - * Depending on whether the batch is progressive or not, the - * Drupal\Core\Queue\Batch or Drupal\Core\Queue\BatchMemory handler classes will - * be used. The name and class of the queue are added by reference to the - * batch set. - * - * @param $batch - * The batch array. - * @param $set_id - * The id of the set to process. - */ -function _batch_populate_queue(&$batch, $set_id) { - $batch_set = &$batch['sets'][$set_id]; - - if (isset($batch_set['operations'])) { - $batch_set += array( - 'queue' => array( - 'name' => 'drupal_batch:' . $batch['id'] . ':' . $set_id, - 'class' => $batch['progressive'] ? 'Drupal\Core\Queue\Batch' : 'Drupal\Core\Queue\BatchMemory', - ), - ); - - $queue = _batch_queue($batch_set); - $queue->createQueue(); - foreach ($batch_set['operations'] as $operation) { - $queue->createItem($operation); - } - - unset($batch_set['operations']); - } + return BatchQueueController::getBatches(); } /** diff --git a/core/lib/Drupal/Core/Batch/Batch.php b/core/lib/Drupal/Core/Batch/Batch.php index eb45a1a..80f804d 100644 --- a/core/lib/Drupal/Core/Batch/Batch.php +++ b/core/lib/Drupal/Core/Batch/Batch.php @@ -2,21 +2,13 @@ namespace Drupal\Core\Batch; -use Drupal\Core\Database\Database; use Drupal\Core\Url; -use Symfony\Component\HttpFoundation\RedirectResponse; /** * Stores a domain object for a batch process. - * - * This object contains the same information than a batch array. - * - * @see batch_set() */ class Batch { - private static $batch = array(); - protected $operations = []; protected $title; protected $init_message; @@ -36,10 +28,10 @@ class Batch { * The title. */ public function __construct($title = NULL) { - $this->setTitle(is_null($title) ? t('Processing') : $title); - $this->setInitMessage(t('Initializing.')); - $this->setProgressMessage(t('Completed @current of @total.')); - $this->setErrorMessage(t('An error has occurred.')); + $this->setTitle(is_null($title) ? 'Processing' : $title); + $this->setInitMessage('Initializing.'); + $this->setProgressMessage('Completed @current of @total.'); + $this->setErrorMessage('An error has occurred.'); } /** @@ -267,47 +259,7 @@ public function addCss($filename) { * @return $this */ public function enqueue() { - // Initialize the batch if needed. - if (empty(self::$batch)) { - self::$batch = array( - 'sets' => array(), - 'has_form_submits' => FALSE, - ); - } - - $init = [ - 'sandbox' => [], - 'results' => [], - 'success' => FALSE, - 'start' => 0, - 'elapsed' => 0, - ]; - $batch_set = $init + $this->toArray(); - - // Tweak init_message to avoid the bottom of the page flickering down after - // init phase. - $batch_set['init_message'] .= '
 '; - - // The non-concurrent workflow of batch execution allows us to save - // numberOfItems() queries by handling our own counter. - $batch_set['total'] = count($batch_set['operations']); - $batch_set['count'] = $batch_set['total']; - // Add the set to the batch. - if (empty(self::$batch['id'])) { - // The batch is not running yet. Simply add the new set. - self::$batch['sets'][] = $batch_set; - } - else { - // The set is being added while the batch is running. Insert the new set - // right after the current one to ensure execution order, and store its - // operations in a queue. - $index = self::$batch['current_set'] + 1; - $slice1 = array_slice(self::$batch['sets'], 0, $index); - $slice2 = array_slice(self::$batch['sets'], $index); - self::$batch['sets'] = array_merge($slice1, array($batch_set), $slice2); - _batch_populate_queue(self::$batch, $index); - } - + BatchQueueController::enqueue($this); return $this; } @@ -337,90 +289,9 @@ public function enqueue() { * @return \Symfony\Component\HttpFoundation\RedirectResponse|null * A redirect response if the batch is progressive. No return value * otherwise. - * - * @deprecated as of Drupal 8.2.x, will be removed in Drupal 9.0.0. Use - * \Drupal\Core\Batch\Batch::process() instead or the process() function on - * an existing Batch object. */ public static function process($redirect = NULL, Url $url = NULL, $redirect_callback = NULL) { - if (isset(self::$batch)) { - - // Add process information. - $process_info = [ - '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, - ]; - self::$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', self::$batch); - - // Assign an arbitrary id: don't rely on a serial column in the 'batch' - // table, since non-progressive batches skip database storage completely. - self::$batch['id'] = Database::getConnection()->nextId(); - - // Move operations to a job queue. Non-progressive batches will use a - // memory-based queue. - foreach (self::$batch['sets'] as $key => $batch_set) { - _batch_populate_queue(self::$batch, $key); - } - - // Initiate processing. - if (self::$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 = self::$batch['url']; - /** @var \Drupal\Core\Url $error_url */ - $error_url = clone $batch_url; - $query_options = $error_url->getOption('query'); - $query_options['id'] = self::$batch['id']; - $query_options['op'] = 'finished'; - $error_url->setOption('query', $query_options); - - self::$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 un-setting the 'destination', if there is any. - $request = \Drupal::request(); - if ($request->query->has('destination')) { - self::$batch['destination'] = $request->query->get('destination'); - $request->query->remove('destination'); - } - - // Store the batch. - \Drupal::service('batch.storage')->create(self::$batch); - - // Set the batch number in the session to guarantee that it will stay - // alive. - $_SESSION['batches'][self::$batch['id']] = TRUE; - - // Redirect for processing. - $query_options = $error_url->getOption('query'); - $query_options['op'] = 'start'; - $query_options['id'] = self::$batch['id']; - $batch_url->setOption('query', $query_options); - if (($function = self::$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. - require_once DRUPAL_ROOT . '/core/includes/batch.inc'; - _batch_process(); - } - } + return BatchQueueController::process($redirect, $url, $redirect_callback); } /** @@ -448,11 +319,4 @@ public function toArray() { return $array; } - /** - * Retrieves the current batch. - */ - public static function &getBatches() { - return Batch::$batch; - } - } diff --git a/core/lib/Drupal/Core/Batch/BatchQueueController.php b/core/lib/Drupal/Core/Batch/BatchQueueController.php new file mode 100644 index 0000000..ff2f637 --- /dev/null +++ b/core/lib/Drupal/Core/Batch/BatchQueueController.php @@ -0,0 +1,228 @@ + array(), + 'has_form_submits' => FALSE, + ); + } + + $init = [ + 'sandbox' => [], + 'results' => [], + 'success' => FALSE, + 'start' => 0, + 'elapsed' => 0, + ]; + $batch_set = $init + $batch->toArray(); + + // Make strings translatable. + if (is_string($batch_set['title'])) { + $batch_set['title'] = new TranslatableMarkup($batch_set['title']); + } + if (is_string($batch_set['init_message'])) { + $batch_set['init_message'] = new TranslatableMarkup($batch_set['init_message']); + } + if (is_string($batch_set['progress_message'])) { + $batch_set['progress_message'] = new TranslatableMarkup($batch_set['progress_message']); + } + if (is_string($batch_set['error_message'])) { + $batch_set['error_message'] = new TranslatableMarkup($batch_set['error_message']); + } + + // The non-concurrent workflow of batch execution allows us to save + // numberOfItems() queries by handling our own counter. + $batch_set['total'] = count($batch_set['operations']); + $batch_set['count'] = $batch_set['total']; + // Add the set to the batch. + if (empty(self::$batches['id'])) { + // The batch is not running yet. Simply add the new set. + self::$batches['sets'][] = $batch_set; + } + else { + // The set is being added while the batch is running. Insert the new set + // right after the current one to ensure execution order, and store its + // operations in a queue. + $index = self::$batches['current_set'] + 1; + $slice1 = array_slice(self::$batches['sets'], 0, $index); + $slice2 = array_slice(self::$batches['sets'], $index); + self::$batches['sets'] = array_merge($slice1, array($batch_set), $slice2); + self::populateQueue($index); + } + + } + + /** + * 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) URL of the batch processing page. + * Should only be used for separate scripts like update.php. + * @param string $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 static function process($redirect = NULL, Url $url = NULL, $redirect_callback = NULL) { + if (isset(self::$batches)) { + + // Add process information. + $process_info = [ + '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, + ]; + self::$batches += $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', self::$batches); + + // Assign an arbitrary id: don't rely on a serial column in the 'batch' + // table, since non-progressive batches skip database storage completely. + self::$batches['id'] = Database::getConnection()->nextId(); + + // Move operations to a job queue. Non-progressive batches will use a + // memory-based queue. + foreach (self::$batches['sets'] as $key => $batch_set) { + self::populateQueue($key); + } + + // Initiate processing. + if (self::$batches['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 = self::$batches['url']; + /** @var \Drupal\Core\Url $error_url */ + $error_url = clone $batch_url; + $query_options = $error_url->getOption('query'); + $query_options['id'] = self::$batches['id']; + $query_options['op'] = 'finished'; + $error_url->setOption('query', $query_options); + + self::$batches['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 un-setting the 'destination', if there is any. + $request = \Drupal::request(); + if ($request->query->has('destination')) { + self::$batches['destination'] = $request->query->get('destination'); + $request->query->remove('destination'); + } + + // Store the batch. + \Drupal::service('batch.storage')->create(self::$batches); + + // Set the batch number in the session to guarantee that it will stay + // alive. + $_SESSION['batches'][self::$batches['id']] = TRUE; + + // Redirect for processing. + $query_options = $error_url->getOption('query'); + $query_options['op'] = 'start'; + $query_options['id'] = self::$batches['id']; + $batch_url->setOption('query', $query_options); + if (($function = self::$batches['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. + require_once DRUPAL_ROOT . '/core/includes/batch.inc'; + _batch_process(); + } + } + + return NULL; + } + + /** + * Retrieves the current batch. + */ + public static function &getBatches() { + return self::$batches; + } + + /** + * Populates a job queue with the operations of a batch set. + * + * Depending on whether the batch is progressive or not, the + * Drupal\Core\Queue\Batch or Drupal\Core\Queue\BatchMemory handler classes + * will be used. The name and class of the queue are added by reference to the + * batch set. + * + * @param int $set_id + * The id of the set to process. + */ + protected static function populateQueue($set_id = 0) { + $batch = &self::$batches; + $batch_set = &$batch['sets'][$set_id]; + + if (isset($batch_set['operations'])) { + $batch_set += array( + 'queue' => array( + 'name' => 'drupal_batch:' . $batch['id'] . ':' . $set_id, + 'class' => $batch['progressive'] ? 'Drupal\Core\Queue\Batch' : 'Drupal\Core\Queue\BatchMemory', + ), + ); + + $queue = _batch_queue($batch_set); + $queue->createQueue(); + foreach ($batch_set['operations'] as $operation) { + $queue->createItem($operation); + } + + unset($batch_set['operations']); + } + } + +}