annotate core/includes/batch.inc @ 19:fa3358dc1485 tip

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents af1871eacc83
children
rev   line source
Chris@0 1 <?php
Chris@0 2
Chris@0 3 /**
Chris@0 4 * @file
Chris@0 5 * Batch processing API for processes to run in multiple HTTP requests.
Chris@0 6 *
Chris@0 7 * Note that batches are usually invoked by form submissions, which is
Chris@0 8 * why the core interaction functions of the batch processing API live in
Chris@0 9 * form.inc.
Chris@0 10 *
Chris@0 11 * @see form.inc
Chris@0 12 * @see batch_set()
Chris@0 13 * @see batch_process()
Chris@0 14 * @see batch_get()
Chris@0 15 */
Chris@0 16
Chris@0 17 use Drupal\Component\Utility\Timer;
Chris@0 18 use Drupal\Component\Utility\UrlHelper;
Chris@0 19 use Drupal\Core\Batch\Percentage;
Chris@0 20 use Drupal\Core\Form\FormState;
Chris@0 21 use Drupal\Core\Url;
Chris@0 22 use Symfony\Component\HttpFoundation\JsonResponse;
Chris@0 23 use Symfony\Component\HttpFoundation\Request;
Chris@0 24 use Symfony\Component\HttpFoundation\RedirectResponse;
Chris@0 25
Chris@0 26 /**
Chris@0 27 * Renders the batch processing page based on the current state of the batch.
Chris@0 28 *
Chris@0 29 * @param \Symfony\Component\HttpFoundation\Request $request
Chris@0 30 * The current request object.
Chris@0 31 *
Chris@0 32 * @see _batch_shutdown()
Chris@0 33 */
Chris@0 34 function _batch_page(Request $request) {
Chris@0 35 $batch = &batch_get();
Chris@0 36
Chris@0 37 if (!($request_id = $request->query->get('id'))) {
Chris@0 38 return FALSE;
Chris@0 39 }
Chris@0 40
Chris@0 41 // Retrieve the current state of the batch.
Chris@0 42 if (!$batch) {
Chris@0 43 $batch = \Drupal::service('batch.storage')->load($request_id);
Chris@0 44 if (!$batch) {
Chris@17 45 \Drupal::messenger()->addError(t('No active batch.'));
Chris@18 46 return new RedirectResponse(Url::fromRoute('<front>', [], ['absolute' => TRUE])->toString());
Chris@0 47 }
Chris@0 48 }
Chris@0 49
Chris@0 50 // We need to store the updated batch information in the batch storage after
Chris@0 51 // processing the batch. In order for the error page to work correctly this
Chris@0 52 // needs to be done even in case of a PHP fatal error in which case the end of
Chris@0 53 // this function is never reached. Therefore we register a shutdown function
Chris@0 54 // to handle this case. Because with FastCGI and fastcgi_finish_request()
Chris@0 55 // shutdown functions are called after the HTTP connection is closed, updating
Chris@0 56 // the batch information in a shutdown function would lead to race conditions
Chris@0 57 // between consecutive requests if the batch processing continues. In case of
Chris@0 58 // a fatal error the processing stops anyway, so it works even with FastCGI.
Chris@0 59 // However, we must ensure to only update in the shutdown phase in this
Chris@0 60 // particular case we track whether the batch information still needs to be
Chris@0 61 // updated.
Chris@0 62 // @see _batch_shutdown()
Chris@0 63 // @see \Symfony\Component\HttpFoundation\Response::send()
Chris@0 64 drupal_register_shutdown_function('_batch_shutdown');
Chris@0 65 _batch_needs_update(TRUE);
Chris@0 66
Chris@0 67 $build = [];
Chris@0 68
Chris@0 69 // Add batch-specific libraries.
Chris@0 70 foreach ($batch['sets'] as $batch_set) {
Chris@0 71 if (isset($batch_set['library'])) {
Chris@0 72 foreach ($batch_set['library'] as $library) {
Chris@0 73 $build['#attached']['library'][] = $library;
Chris@0 74 }
Chris@0 75 }
Chris@0 76 }
Chris@0 77
Chris@0 78 $op = $request->query->get('op', '');
Chris@0 79 switch ($op) {
Chris@0 80 case 'start':
Chris@0 81 case 'do_nojs':
Chris@0 82 // Display the full progress page on startup and on each additional
Chris@0 83 // non-JavaScript iteration.
Chris@0 84 $current_set = _batch_current_set();
Chris@0 85 $build['#title'] = $current_set['title'];
Chris@0 86 $build['content'] = _batch_progress_page();
Chris@0 87
Chris@0 88 $response = $build;
Chris@0 89 break;
Chris@0 90
Chris@0 91 case 'do':
Chris@0 92 // JavaScript-based progress page callback.
Chris@0 93 $response = _batch_do();
Chris@0 94 break;
Chris@0 95
Chris@0 96 case 'finished':
Chris@0 97 // _batch_finished() returns a RedirectResponse.
Chris@0 98 $response = _batch_finished();
Chris@0 99 break;
Chris@0 100 }
Chris@0 101
Chris@0 102 if ($batch) {
Chris@0 103 \Drupal::service('batch.storage')->update($batch);
Chris@0 104 }
Chris@0 105 _batch_needs_update(FALSE);
Chris@0 106
Chris@0 107 return $response;
Chris@0 108 }
Chris@0 109
Chris@0 110 /**
Chris@0 111 * Checks whether the batch information needs to be updated in the storage.
Chris@0 112 *
Chris@0 113 * @param bool $new_value
Chris@0 114 * (optional) A new value to set.
Chris@0 115 *
Chris@0 116 * @return bool
Chris@0 117 * TRUE if the batch information needs to be updated; FALSE otherwise.
Chris@0 118 */
Chris@0 119 function _batch_needs_update($new_value = NULL) {
Chris@0 120 $needs_update = &drupal_static(__FUNCTION__, FALSE);
Chris@0 121
Chris@0 122 if (isset($new_value)) {
Chris@0 123 $needs_update = $new_value;
Chris@0 124 }
Chris@0 125
Chris@0 126 return $needs_update;
Chris@0 127 }
Chris@0 128
Chris@0 129 /**
Chris@0 130 * Does one execution pass with JavaScript and returns progress to the browser.
Chris@0 131 *
Chris@0 132 * @see _batch_progress_page_js()
Chris@0 133 * @see _batch_process()
Chris@0 134 */
Chris@0 135 function _batch_do() {
Chris@0 136 // Perform actual processing.
Chris@0 137 list($percentage, $message, $label) = _batch_process();
Chris@0 138
Chris@0 139 return new JsonResponse(['status' => TRUE, 'percentage' => $percentage, 'message' => $message, 'label' => $label]);
Chris@0 140 }
Chris@0 141
Chris@0 142 /**
Chris@0 143 * Outputs a batch processing page.
Chris@0 144 *
Chris@0 145 * @see _batch_process()
Chris@0 146 */
Chris@0 147 function _batch_progress_page() {
Chris@0 148 $batch = &batch_get();
Chris@0 149
Chris@0 150 $current_set = _batch_current_set();
Chris@0 151
Chris@0 152 $new_op = 'do_nojs';
Chris@0 153
Chris@0 154 if (!isset($batch['running'])) {
Chris@0 155 // This is the first page so we return some output immediately.
Chris@0 156 $percentage = 0;
Chris@0 157 $message = $current_set['init_message'];
Chris@0 158 $label = '';
Chris@0 159 $batch['running'] = TRUE;
Chris@0 160 }
Chris@0 161 else {
Chris@0 162 // This is one of the later requests; do some processing first.
Chris@0 163
Chris@0 164 // Error handling: if PHP dies due to a fatal error (e.g. a nonexistent
Chris@0 165 // function), it will output whatever is in the output buffer, followed by
Chris@0 166 // the error message.
Chris@0 167 ob_start();
Chris@0 168 $fallback = $current_set['error_message'] . '<br />' . $batch['error_message'];
Chris@0 169
Chris@0 170 // We strip the end of the page using a marker in the template, so any
Chris@0 171 // additional HTML output by PHP shows up inside the page rather than below
Chris@0 172 // it. While this causes invalid HTML, the same would be true if we didn't,
Chris@0 173 // as content is not allowed to appear after </html> anyway.
Chris@0 174 $bare_html_page_renderer = \Drupal::service('bare_html_page_renderer');
Chris@0 175 $response = $bare_html_page_renderer->renderBarePage(['#markup' => $fallback], $current_set['title'], 'maintenance_page', [
Chris@0 176 '#show_messages' => FALSE,
Chris@0 177 ]);
Chris@0 178
Chris@0 179 // Just use the content of the response.
Chris@0 180 $fallback = $response->getContent();
Chris@0 181
Chris@0 182 list($fallback) = explode('<!--partial-->', $fallback);
Chris@0 183 print $fallback;
Chris@0 184
Chris@0 185 // Perform actual processing.
Chris@0 186 list($percentage, $message, $label) = _batch_process($batch);
Chris@0 187 if ($percentage == 100) {
Chris@0 188 $new_op = 'finished';
Chris@0 189 }
Chris@0 190
Chris@0 191 // PHP did not die; remove the fallback output.
Chris@0 192 ob_end_clean();
Chris@0 193 }
Chris@0 194
Chris@0 195 // Merge required query parameters for batch processing into those provided by
Chris@0 196 // batch_set() or hook_batch_alter().
Chris@0 197 $query_options = $batch['url']->getOption('query');
Chris@0 198 $query_options['id'] = $batch['id'];
Chris@0 199 $query_options['op'] = $new_op;
Chris@0 200 $batch['url']->setOption('query', $query_options);
Chris@0 201
Chris@0 202 $url = $batch['url']->toString(TRUE)->getGeneratedUrl();
Chris@0 203
Chris@0 204 $build = [
Chris@0 205 '#theme' => 'progress_bar',
Chris@0 206 '#percent' => $percentage,
Chris@0 207 '#message' => ['#markup' => $message],
Chris@0 208 '#label' => $label,
Chris@0 209 '#attached' => [
Chris@0 210 'html_head' => [
Chris@0 211 [
Chris@0 212 [
Chris@0 213 // Redirect through a 'Refresh' meta tag if JavaScript is disabled.
Chris@0 214 '#tag' => 'meta',
Chris@0 215 '#noscript' => TRUE,
Chris@0 216 '#attributes' => [
Chris@0 217 'http-equiv' => 'Refresh',
Chris@0 218 'content' => '0; URL=' . $url,
Chris@0 219 ],
Chris@0 220 ],
Chris@0 221 'batch_progress_meta_refresh',
Chris@0 222 ],
Chris@0 223 ],
Chris@0 224 // Adds JavaScript code and settings for clients where JavaScript is enabled.
Chris@0 225 'drupalSettings' => [
Chris@0 226 'batch' => [
Chris@0 227 'errorMessage' => $current_set['error_message'] . '<br />' . $batch['error_message'],
Chris@0 228 'initMessage' => $current_set['init_message'],
Chris@0 229 'uri' => $url,
Chris@0 230 ],
Chris@0 231 ],
Chris@0 232 'library' => [
Chris@0 233 'core/drupal.batch',
Chris@0 234 ],
Chris@0 235 ],
Chris@0 236 ];
Chris@0 237 return $build;
Chris@0 238 }
Chris@0 239
Chris@0 240 /**
Chris@0 241 * Processes sets in a batch.
Chris@0 242 *
Chris@0 243 * If the batch was marked for progressive execution (default), this executes as
Chris@0 244 * many operations in batch sets until an execution time of 1 second has been
Chris@0 245 * exceeded. It will continue with the next operation of the same batch set in
Chris@0 246 * the next request.
Chris@0 247 *
Chris@0 248 * @return array
Chris@0 249 * An array containing a completion value (in percent) and a status message.
Chris@0 250 */
Chris@0 251 function _batch_process() {
Chris@0 252 $batch = &batch_get();
Chris@0 253 $current_set = &_batch_current_set();
Chris@0 254 // Indicate that this batch set needs to be initialized.
Chris@0 255 $set_changed = TRUE;
Chris@18 256 $task_message = '';
Chris@0 257
Chris@0 258 // If this batch was marked for progressive execution (e.g. forms submitted by
Chris@0 259 // \Drupal::formBuilder()->submitForm(), initialize a timer to determine
Chris@0 260 // whether we need to proceed with the same batch phase when a processing time
Chris@0 261 // of 1 second has been exceeded.
Chris@0 262 if ($batch['progressive']) {
Chris@0 263 Timer::start('batch_processing');
Chris@0 264 }
Chris@0 265
Chris@0 266 if (empty($current_set['start'])) {
Chris@0 267 $current_set['start'] = microtime(TRUE);
Chris@0 268 }
Chris@0 269
Chris@0 270 $queue = _batch_queue($current_set);
Chris@0 271
Chris@0 272 while (!$current_set['success']) {
Chris@0 273 // If this is the first time we iterate this batch set in the current
Chris@0 274 // request, we check if it requires an additional file for functions
Chris@0 275 // definitions.
Chris@0 276 if ($set_changed && isset($current_set['file']) && is_file($current_set['file'])) {
Chris@0 277 include_once \Drupal::root() . '/' . $current_set['file'];
Chris@0 278 }
Chris@0 279
Chris@18 280 $task_message = '';
Chris@0 281 // Assume a single pass operation and set the completion level to 1 by
Chris@0 282 // default.
Chris@0 283 $finished = 1;
Chris@0 284
Chris@0 285 if ($item = $queue->claimItem()) {
Chris@0 286 list($callback, $args) = $item->data;
Chris@0 287
Chris@0 288 // Build the 'context' array and execute the function call.
Chris@0 289 $batch_context = [
Chris@0 290 'sandbox' => &$current_set['sandbox'],
Chris@0 291 'results' => &$current_set['results'],
Chris@0 292 'finished' => &$finished,
Chris@0 293 'message' => &$task_message,
Chris@0 294 ];
Chris@0 295 call_user_func_array($callback, array_merge($args, [&$batch_context]));
Chris@0 296
Chris@0 297 if ($finished >= 1) {
Chris@0 298 // Make sure this step is not counted twice when computing $current.
Chris@0 299 $finished = 0;
Chris@0 300 // Remove the processed operation and clear the sandbox.
Chris@0 301 $queue->deleteItem($item);
Chris@0 302 $current_set['count']--;
Chris@0 303 $current_set['sandbox'] = [];
Chris@0 304 }
Chris@0 305 }
Chris@0 306
Chris@0 307 // When all operations in the current batch set are completed, browse
Chris@0 308 // through the remaining sets, marking them 'successfully processed'
Chris@0 309 // along the way, until we find a set that contains operations.
Chris@0 310 // _batch_next_set() executes form submit handlers stored in 'control'
Chris@0 311 // sets (see \Drupal::service('form_submitter')), which can in turn add new
Chris@0 312 // sets to the batch.
Chris@0 313 $set_changed = FALSE;
Chris@0 314 $old_set = $current_set;
Chris@0 315 while (empty($current_set['count']) && ($current_set['success'] = TRUE) && _batch_next_set()) {
Chris@0 316 $current_set = &_batch_current_set();
Chris@0 317 $current_set['start'] = microtime(TRUE);
Chris@0 318 $set_changed = TRUE;
Chris@0 319 }
Chris@0 320
Chris@0 321 // At this point, either $current_set contains operations that need to be
Chris@0 322 // processed or all sets have been completed.
Chris@0 323 $queue = _batch_queue($current_set);
Chris@0 324
Chris@0 325 // If we are in progressive mode, break processing after 1 second.
Chris@0 326 if ($batch['progressive'] && Timer::read('batch_processing') > 1000) {
Chris@0 327 // Record elapsed wall clock time.
Chris@0 328 $current_set['elapsed'] = round((microtime(TRUE) - $current_set['start']) * 1000, 2);
Chris@0 329 break;
Chris@0 330 }
Chris@0 331 }
Chris@0 332
Chris@0 333 if ($batch['progressive']) {
Chris@0 334 // Gather progress information.
Chris@0 335
Chris@0 336 // Reporting 100% progress will cause the whole batch to be considered
Chris@0 337 // processed. If processing was paused right after moving to a new set,
Chris@0 338 // we have to use the info from the new (unprocessed) set.
Chris@0 339 if ($set_changed && isset($current_set['queue'])) {
Chris@0 340 // Processing will continue with a fresh batch set.
Chris@0 341 $remaining = $current_set['count'];
Chris@0 342 $total = $current_set['total'];
Chris@0 343 $progress_message = $current_set['init_message'];
Chris@0 344 $task_message = '';
Chris@0 345 }
Chris@0 346 else {
Chris@0 347 // Processing will continue with the current batch set.
Chris@0 348 $remaining = $old_set['count'];
Chris@0 349 $total = $old_set['total'];
Chris@0 350 $progress_message = $old_set['progress_message'];
Chris@0 351 }
Chris@0 352
Chris@0 353 // Total progress is the number of operations that have fully run plus the
Chris@0 354 // completion level of the current operation.
Chris@0 355 $current = $total - $remaining + $finished;
Chris@0 356 $percentage = _batch_api_percentage($total, $current);
Chris@0 357 $elapsed = isset($current_set['elapsed']) ? $current_set['elapsed'] : 0;
Chris@0 358 $values = [
Chris@0 359 '@remaining' => $remaining,
Chris@0 360 '@total' => $total,
Chris@0 361 '@current' => floor($current),
Chris@0 362 '@percentage' => $percentage,
Chris@0 363 '@elapsed' => \Drupal::service('date.formatter')->formatInterval($elapsed / 1000),
Chris@0 364 // If possible, estimate remaining processing time.
Chris@0 365 '@estimate' => ($current > 0) ? \Drupal::service('date.formatter')->formatInterval(($elapsed * ($total - $current) / $current) / 1000) : '-',
Chris@0 366 ];
Chris@0 367 $message = strtr($progress_message, $values);
Chris@0 368
Chris@18 369 return [$percentage, $message, $task_message];
Chris@0 370 }
Chris@0 371 else {
Chris@0 372 // If we are not in progressive mode, the entire batch has been processed.
Chris@0 373 return _batch_finished();
Chris@0 374 }
Chris@0 375 }
Chris@0 376
Chris@0 377 /**
Chris@0 378 * Formats the percent completion for a batch set.
Chris@0 379 *
Chris@0 380 * @param int $total
Chris@0 381 * The total number of operations.
Chris@0 382 * @param int|float $current
Chris@0 383 * The number of the current operation. This may be a floating point number
Chris@0 384 * rather than an integer in the case of a multi-step operation that is not
Chris@0 385 * yet complete; in that case, the fractional part of $current represents the
Chris@0 386 * fraction of the operation that has been completed.
Chris@0 387 *
Chris@0 388 * @return string
Chris@0 389 * The properly formatted percentage, as a string. We output percentages
Chris@0 390 * using the correct number of decimal places so that we never print "100%"
Chris@0 391 * until we are finished, but we also never print more decimal places than
Chris@0 392 * are meaningful.
Chris@0 393 *
Chris@0 394 * @see _batch_process()
Chris@0 395 */
Chris@0 396 function _batch_api_percentage($total, $current) {
Chris@0 397 return Percentage::format($total, $current);
Chris@0 398 }
Chris@0 399
Chris@0 400 /**
Chris@0 401 * Returns the batch set being currently processed.
Chris@0 402 */
Chris@0 403 function &_batch_current_set() {
Chris@0 404 $batch = &batch_get();
Chris@0 405 return $batch['sets'][$batch['current_set']];
Chris@0 406 }
Chris@0 407
Chris@0 408 /**
Chris@0 409 * Retrieves the next set in a batch.
Chris@0 410 *
Chris@0 411 * If there is a subsequent set in this batch, assign it as the new set to
Chris@0 412 * process and execute its form submit handler (if defined), which may add
Chris@0 413 * further sets to this batch.
Chris@0 414 *
Chris@0 415 * @return true|null
Chris@0 416 * TRUE if a subsequent set was found in the batch; no value will be returned
Chris@0 417 * if no subsequent set was found.
Chris@0 418 */
Chris@0 419 function _batch_next_set() {
Chris@0 420 $batch = &batch_get();
Chris@0 421 if (isset($batch['sets'][$batch['current_set'] + 1])) {
Chris@0 422 $batch['current_set']++;
Chris@0 423 $current_set = &_batch_current_set();
Chris@0 424 if (isset($current_set['form_submit']) && ($callback = $current_set['form_submit']) && is_callable($callback)) {
Chris@0 425 // We use our stored copies of $form and $form_state to account for
Chris@0 426 // possible alterations by previous form submit handlers.
Chris@0 427 $complete_form = &$batch['form_state']->getCompleteForm();
Chris@0 428 call_user_func_array($callback, [&$complete_form, &$batch['form_state']]);
Chris@0 429 }
Chris@0 430 return TRUE;
Chris@0 431 }
Chris@0 432 }
Chris@0 433
Chris@0 434 /**
Chris@0 435 * Ends the batch processing.
Chris@0 436 *
Chris@0 437 * Call the 'finished' callback of each batch set to allow custom handling of
Chris@0 438 * the results and resolve page redirection.
Chris@0 439 */
Chris@0 440 function _batch_finished() {
Chris@0 441 $batch = &batch_get();
Chris@0 442 $batch_finished_redirect = NULL;
Chris@0 443
Chris@0 444 // Execute the 'finished' callbacks for each batch set, if defined.
Chris@0 445 foreach ($batch['sets'] as $batch_set) {
Chris@0 446 if (isset($batch_set['finished'])) {
Chris@0 447 // Check if the set requires an additional file for function definitions.
Chris@0 448 if (isset($batch_set['file']) && is_file($batch_set['file'])) {
Chris@0 449 include_once \Drupal::root() . '/' . $batch_set['file'];
Chris@0 450 }
Chris@0 451 if (is_callable($batch_set['finished'])) {
Chris@0 452 $queue = _batch_queue($batch_set);
Chris@0 453 $operations = $queue->getAllItems();
Chris@0 454 $batch_set_result = call_user_func_array($batch_set['finished'], [$batch_set['success'], $batch_set['results'], $operations, \Drupal::service('date.formatter')->formatInterval($batch_set['elapsed'] / 1000)]);
Chris@0 455 // If a batch 'finished' callback requested a redirect after the batch
Chris@0 456 // is complete, save that for later use. If more than one batch set
Chris@0 457 // returned a redirect, the last one is used.
Chris@0 458 if ($batch_set_result instanceof RedirectResponse) {
Chris@0 459 $batch_finished_redirect = $batch_set_result;
Chris@0 460 }
Chris@0 461 }
Chris@0 462 }
Chris@0 463 }
Chris@0 464
Chris@0 465 // Clean up the batch table and unset the static $batch variable.
Chris@0 466 if ($batch['progressive']) {
Chris@0 467 \Drupal::service('batch.storage')->delete($batch['id']);
Chris@0 468 foreach ($batch['sets'] as $batch_set) {
Chris@0 469 if ($queue = _batch_queue($batch_set)) {
Chris@0 470 $queue->deleteQueue();
Chris@0 471 }
Chris@0 472 }
Chris@0 473 // Clean-up the session. Not needed for CLI updates.
Chris@0 474 if (isset($_SESSION)) {
Chris@0 475 unset($_SESSION['batches'][$batch['id']]);
Chris@0 476 if (empty($_SESSION['batches'])) {
Chris@0 477 unset($_SESSION['batches']);
Chris@0 478 }
Chris@0 479 }
Chris@0 480 }
Chris@0 481 $_batch = $batch;
Chris@0 482 $batch = NULL;
Chris@0 483
Chris@0 484 // Redirect if needed.
Chris@0 485 if ($_batch['progressive']) {
Chris@0 486 // Revert the 'destination' that was saved in batch_process().
Chris@0 487 if (isset($_batch['destination'])) {
Chris@0 488 \Drupal::request()->query->set('destination', $_batch['destination']);
Chris@0 489 }
Chris@0 490
Chris@0 491 // Determine the target path to redirect to. If a batch 'finished' callback
Chris@0 492 // returned a redirect response object, use that. Otherwise, fall back on
Chris@0 493 // the form redirection.
Chris@0 494 if (isset($batch_finished_redirect)) {
Chris@0 495 return $batch_finished_redirect;
Chris@0 496 }
Chris@0 497 elseif (!isset($_batch['form_state'])) {
Chris@0 498 $_batch['form_state'] = new FormState();
Chris@0 499 }
Chris@0 500 if ($_batch['form_state']->getRedirect() === NULL) {
Chris@0 501 $redirect = $_batch['batch_redirect'] ?: $_batch['source_url'];
Chris@0 502 // Any path with a scheme does not correspond to a route.
Chris@0 503 if (!$redirect instanceof Url) {
Chris@0 504 $options = UrlHelper::parse($redirect);
Chris@0 505 if (parse_url($options['path'], PHP_URL_SCHEME)) {
Chris@0 506 $redirect = Url::fromUri($options['path'], $options);
Chris@0 507 }
Chris@0 508 else {
Chris@0 509 $redirect = \Drupal::pathValidator()->getUrlIfValid($options['path']);
Chris@0 510 if (!$redirect) {
Chris@0 511 // Stay on the same page if the redirect was invalid.
Chris@0 512 $redirect = Url::fromRoute('<current>');
Chris@0 513 }
Chris@0 514 $redirect->setOptions($options);
Chris@0 515 }
Chris@0 516 }
Chris@0 517 $_batch['form_state']->setRedirectUrl($redirect);
Chris@0 518 }
Chris@0 519
Chris@0 520 // Use \Drupal\Core\Form\FormSubmitterInterface::redirectForm() to handle
Chris@0 521 // the redirection logic.
Chris@0 522 $redirect = \Drupal::service('form_submitter')->redirectForm($_batch['form_state']);
Chris@0 523 if (is_object($redirect)) {
Chris@0 524 return $redirect;
Chris@0 525 }
Chris@0 526
Chris@0 527 // If no redirection happened, redirect to the originating page. In case the
Chris@0 528 // form needs to be rebuilt, save the final $form_state for
Chris@0 529 // \Drupal\Core\Form\FormBuilderInterface::buildForm().
Chris@0 530 if ($_batch['form_state']->isRebuilding()) {
Chris@0 531 $_SESSION['batch_form_state'] = $_batch['form_state'];
Chris@0 532 }
Chris@0 533 $callback = $_batch['redirect_callback'];
Chris@0 534 $_batch['source_url']->mergeOptions(['query' => ['op' => 'finish', 'id' => $_batch['id']]]);
Chris@0 535 if (is_callable($callback)) {
Chris@0 536 $callback($_batch['source_url'], $_batch['source_url']->getOption('query'));
Chris@0 537 }
Chris@0 538 elseif ($callback === NULL) {
Chris@0 539 // Default to RedirectResponse objects when nothing specified.
Chris@0 540 return new RedirectResponse($_batch['source_url']->setAbsolute()->toString());
Chris@0 541 }
Chris@0 542 }
Chris@0 543 }
Chris@0 544
Chris@0 545 /**
Chris@0 546 * Shutdown function: Stores the current batch data for the next request.
Chris@0 547 *
Chris@0 548 * @see _batch_page()
Chris@0 549 * @see drupal_register_shutdown_function()
Chris@0 550 */
Chris@0 551 function _batch_shutdown() {
Chris@0 552 if (($batch = batch_get()) && _batch_needs_update()) {
Chris@0 553 \Drupal::service('batch.storage')->update($batch);
Chris@0 554 }
Chris@0 555 }