We did not manage to convert Batch API to fully leverage the AJAX framework for D7. The current page callback of the /batch path is a weird mix of HTML and JSON. If we would have fully converted Batch API already, then the /batch path would conditionally use ajax_deliver() when needed, and drupal_deliver_html_page() when returning HTML.

Right now, the documented way to alter the delivery callback for AJAX requests breaks all batches:

// @todo Should totally live in core instead of in an API example?
//   Without this, any AJAX stuff returns a full HTML page...
function core_tabs_page_delivery_callback_alter(&$callback) {
  // @todo Fix Batch API.
  if ($_GET['q'] == 'batch') {
    return;
  }
  // jQuery sets a HTTP_X_REQUESTED_WITH header of 'XMLHttpRequest'.
  // If a page would normally be delivered as an html page, and it is called
  // from jQuery, deliver it instead as an AJAX response.
  if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest' && $callback == 'drupal_deliver_html_page') {
    $callback = 'ajax_deliver';
  }
}

Attached patch is an initial attempt to resolve the problem of batches breaking when universally altering the delivery callback as in the documented example.

Comments

yched’s picture

subscribe

I did not really look into D7's page_delivery_callback mechanism so far.

Should we use proper / separate delivery callbacks for 'batch - JS mode' and 'batch - noJS mode' ?

sun’s picture

By default (i.e., without additional drupal_alter()), a single menu router item only has a single delivery callback. Basically meaning that one router path is expected to result in a certain output.

Drupal core implements two delivery callbacks as of now:

1) HTML (page)
2) AJAX (JSON command array)

The /batch path and page callback http://api.drupal.org/api/function/_batch_page/7 is somewhat special, as it

a) Outputs the initial Batch HTML page
http://api.drupal.org/api/function/_batch_start/7

b) If the client has the has_js cookie, seems to respond with JSON batch status data for progress.js
http://api.drupal.org/api/function/_batch_progress_page_js/7
http://api.drupal.org/api/function/_batch_do/7

c) If the client has no has_js cookie, seems to return a full HTML page
http://api.drupal.org/api/function/_batch_progress_page_nojs/7

d) Outputs the finished Batch HTML page
http://api.drupal.org/api/function/_batch_finished/7

Normally, one would likely say that a clear separation would use two different router items:

batch/page - All HTML pages that are output, using the regular HTML delivery callback.
batch/json - JSON output for the has_js cookie case.

"/page" only added for clarity. Technically doable would also be:

batch
batch/json

Note that batch/json would need a new built-in delivery callback for plain JSON, since http://api.drupal.org/api/function/ajax_deliver/7 relies on AJAX framework and command structures.

yched’s picture

Yup. Put that way, that's not too nice :-).
IIRC, this in fact hasn't changed much since the 'progressive processing roundtrip' code that was taken out of D5 update.php and generalized into D6 batch processing.

The constraint is also that standalone scripts (install.php, update.php, D7's authorize.php if I'm not mistaken) also need to use batch API and output a progress page at their own URL outside of the 'system/batch' menu entry. This is currently done by simply including the output of _batch_page(), which internally takes care of doing the right stuff depending on the batch 'state' (start, do, finish, error)

sun’s picture

Thinking through this, I think that the current patch is all we can do for D7.

yched’s picture

If limited to this scope, I'm not sure I get what the patch buys us, then.

What it does is just move the 'force page display without messages' out of the page callback and into a delivery callback. Does it mean that page delivery callback is the official 'right' place if you want to display a page without messages (or sidebars, or both) ?

sun’s picture

The primary problem is that current HEAD

  1. does not use a special delivery callback for the /batch path
  2. so it uses the default delivery callback drupal_deliver_html_page()
  3. drupal_deliver_html_page() is supposed to return HTML
  4. but _batch_page() returns sometimes HTML, sometimes JSON (Note: plain JSON, not AJAX commands)
  5. which ultimately breaks modules that rightfully assume they can dynamically deliver HTML responses in a different way/format
  6. and also invalidates existing hook_page_delivery_callback_alter() API docs (as shown in the OP)

The idea is to do a stopgap fix by adding a dedicated/special delivery callback for the /batch path. This delivery callback can return mixed data formats to its liking without breaking and invalidating the unique purpose of dedicated standard delivery callbacks.

In D8, we can clean this up properly by adding a standard delivery callback for plain JSON and ideally also rewrite the Batch API/UI to remove the current arbitration that happens in _batch_page().

yched’s picture

Status: Needs review » Reviewed & tested by the community
StatusFileSize
new2.66 KB

OK - tested the patch with and without JS with the batch_test module that supports the test suite (manual test because the test suite cannot test the JS mode), everything works fine. Standalone scripts are OK, the patch doesn't touch anything in there (tested with updates for peace of mind).

RTBC, just added minor changes :
- Added some comments documenting the rationale above.
- renamed a variable to $page in batch_deliver_page(), to avoid $page_callback_result being one thing and then another
- in case $page_callback_result === FALSE, do not run drupal_deliver_html_page() twice

sun’s picture

Thanks!

webchick’s picture

Version: 7.x-dev » 8.x-dev

I'm sorry, but no. It's too late now for polish stuff like this.

To reiterate, Drupal 7 is effectively done. The only thing we do at this point is fix bugs that get us to release. This is a nice improvement, but no one is actively blocked from using the batch API at this point in time, so refactoring it is not D7 material.

sun’s picture

Version: 8.x-dev » 7.x-dev
Category: task » bug

The only thing we do at this point is fix bugs that get us to release.

Right, that's what this patch does. I had to learn the hard way that my module - which is strictly following API docs and examples - unintentionally breaks Batch API's processing.

It took me a couple of hours to figure out what on earth broke all kind of batches I tried to run (tests, updates, etc; in short: all batches). All I got was a JavaScript alert stating "An error occurred.", followed by a dump of some response garbage.

Since debugging is close to impossible, I had to disable all modules one after the other to figure out which code or module is guilty for that. I totally did not expect my module to be guilty, because as mentioned, its hook implementation is 1:1 copypasted from the API documentation.

And it turns out that both my module as well as the API documentation is correct. Batch API's page callback is what is not correct, because it sometimes returns plain JSON within a HTML context; i.e., without ending the request. In turn, the JSON page callback result is handed over to the default delivery callback, drupal_deliver_html_page(), which, as the name implies, is expected to return a renderable array or a string to output as HTML.

Handing a plain JSON data array to drupal_deliver_html_page() is technically and conceptually invalid.

Lastly, we already discussed the "proper fix" and "nice improvement" earlier in this issue, but that is definitely D8 material at this point, as it would require to rewrite most of batch.inc.

Thus, this patch is a stopgap fix to make Batch API's page callback not hi-jack the intended purpose and expected API behavior of page callbacks and delivery callbacks.

webchick’s picture

Status: Reviewed & tested by the community » Needs work

IMO then we should merely fix the confusing documentation. But in any case, we can't remove/rename API functions at this point.

yched’s picture

@webchick : I'll let sun confirm, but if I got this right, this is not about the documentation being confusing - the doc is correct, and shows the right way to use hook_page_delivery_callback_alter().

The problem is that the page callback for the 'batch/' menu entry breaks the contract by using drupal_deliver_html_page() to deliver non-HTML data. Any module willing to use hook_page_delivery_callback_alter() to deliver HTML content in a different way is then bound to choke on batches.

The only func remove here is system_batch_page(), the current page callback for 'batch/'. I don't think this qualifies as an API change ?

webchick’s picture

We've been bitten already a few times in beta for changing names of things that shouldn't be a big deal. There's no bug-fix reason to do it here; it's merely polish.

Is this situation a regression from 6.x?

sun’s picture

Status: Needs work » Needs review

The entire concept of delivery callbacks does not exist in D6, it is new in D7.

yched's summary in #12 is correct, especially the part about breaking the "contract" through having a delivery callback, which is intended to deliver HTML, returning JSON.

But of course, this change technically must be considered as an API change. Even though I don't know of any code or module that is trying to re-use any functionality of the /batch menu router item resp. page callback. The only actual API change is that the system_batch_page() callback no longer exists.

AFAICS, the only other way out would be to preemptively end the page request in _batch_do(); i.e., right after the JSON has been generated, so the entire regular page delivery mechanism is not even invoked in the first place. That, however, should be considered as a hack, disallowing any further request modifications by contributed modules, as the request is ended preemptively.

sun’s picture

I hope that the additional reasoning provided in #14 sufficiently explains the need for this patch.

As already mentioned in there, we can also squeeze a drupal_exit() right after the JSON output is generated, but that would be a hacky resolution compared to the existing patch.

cyberwolf’s picture

Subscribing.

nod_’s picture

Version: 7.x-dev » 8.x-dev

What's the status of this taking into consideration the patch removing has_js cookie #229825: backport "$_COOKIE['has_js'] must die" patch to 7.x?

sun’s picture

Issue tags: +kernel-followup
StatusFileSize
new4.37 KB

Re-rolled against HEAD - including HttpKernel changes.

Status: Needs review » Needs work

The last submitted patch, drupal8.batch-delivery-callback.18.patch, failed testing.

Crell’s picture

Title: Make /batch use a dedicated delivery callback » Make /batch use proper response objects
+++ b/core/includes/batch.inc
@@ -96,6 +103,26 @@ function _batch_page() {
 /**
+ * Delivery callback for batch requests.
+ *
+ * @see drupal_deliver_page()
+ * @see drupal_deliver_html_page()
+ */
+function batch_deliver_page($page_callback_result) {
+  if ($page_callback_result instanceof Response) {
+    return $page_callback_result;
+  }
+  elseif (isset($page_callback_result) && (is_string($page_callback_result) || is_array($page_callback_result))) {
+    // Force a page without blocks or messages to display a list of collected
+    // messages later.
+    drupal_set_page_content($page_callback_result);
+    $page = element_info('page');
+    $page['#show_messages'] = FALSE;
+    drupal_deliver_html_page($page);
+  }
+}

Delivery callbacks are about to be removed entirely: http://drupal.org/node/1677304

-16 days to next Drupal core point release.

sun’s picture

Status: Needs work » Needs review
Issue tags: +API clean-up
StatusFileSize
new3.53 KB

So if I get this right, then attached patch should actually work.

Status: Needs review » Needs work

The last submitted patch, kernel.batch_.21.patch, failed testing.

sun’s picture

Status: Needs work » Needs review
StatusFileSize
new848 bytes
new3.88 KB

ae71db1 Fixed JavaScript progress callback response contains Ajax settings.

Status: Needs review » Needs work

The last submitted patch, kernel.batch_.23.patch, failed testing.

sun’s picture

Status: Needs work » Needs review
StatusFileSize
new7.02 KB
new9.22 KB

I'd like to even go one step further, and also change the router item definition for /batch to define proper callbacks and arguments.

@Crell: What's the new/post-kernel way to handle the #show_messages flag that exists for theme_page()?

Batch API uses that to mute any messages that may be set during the operations, and to instead display them at the end of the batch run.

d6360da - #929506: Menu router does not load the 'file' before attempting to invoke callbacks, and fails to find/register them during building
47225ec Replaced custom batch loading with proper router item definition.
a3a6837 Fixed install_run_task() to properly handle _batch_page() return values.

The installer works flawlessly with attached patch - if this fails the testbot again, then I'm not really sure what's going on with the testbot.

Status: Needs review » Needs work

The last submitted patch, kernel.batch_.25.patch, failed testing.

Crell’s picture

What is #show_messages supposed to do now? "Flag that causes something to change vastly elsewhere" is not a design that is encouraged in the post-kernel world, so I'm unclear what the use case is.

marvil07’s picture

Status: Needs work » Needs review
StatusFileSize
new415 bytes
new9.63 KB

The bot fail because of "Missing argument 1 for _batch_page(), called in /var/lib/drupaltestbot/sites/default/files/checkout/core/update.php on line 500 and defined" several times, so I just added a line to pass it the required parameter to _batch_page().

Let's see what bot thinks.

sun’s picture

Coolio, thanks @marvil07!

I guess that leaves us with the #show_messages issue only.

In a progressive batch, Batch API renders the intermediate pages being output for batch operation sets/steps and enforces no messages to be shown for those (as they wouldn't be noticeable, since the page immediately redirects), so that any messages being set are collected during the batch processing, and only output on the final batch finished page.

To experience this behavior, I think one has to disable JavaScript and then execute any progressive batch through the UI; e.g., installing Drupal, importing translations, running update.php, or possibly the most simple, checking for module updates with Update module. (not sure whether there is a simpler one) — OH. Running tests! :-D

However, you'd have to manually implant a call to drupal_set_message() into the batch operation step callback(s), so as to actually make sure that there are messages that should not be output until the batch has finished.

If that still works with this patch, then I think we're ready to go.

yched’s picture

Current patch means token checks only happen in a new separate menu access callback for ?batch/%bid', meaning standalone scripts like update.php or install.php do no tken checks anymore, which sounds annoying.

I can't remember whether pre-D6 update.php (which batch API derived from) had those checks, or whether they were added by Batch API for the more generic use cases.

Status: Needs review » Needs work

The last submitted patch, 28: batch-with-response-28.patch, failed testing.

mgifford’s picture

Version: 8.0.x-dev » 8.1.x-dev
Assigned: sun » Unassigned
Issue summary: View changes

This still a concern in D8? Unassigned issue too.

Version: 8.1.x-dev » 8.2.x-dev

Drupal 8.1.0-beta1 was released on March 2, 2016, which means new developments and disruptive changes should now be targeted against the 8.2.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.2.x-dev » 8.3.x-dev

Drupal 8.2.0-beta1 was released on August 3, 2016, which means new developments and disruptive changes should now be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.0-alpha1 will be released the week of January 30, 2017, which means new developments and disruptive changes should now be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.0-alpha1 will be released the week of July 31, 2017, which means new developments and disruptive changes should now be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.0-alpha1 will be released the week of January 17, 2018, which means new developments and disruptive changes should now be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.6.x-dev » 8.7.x-dev

Drupal 8.6.0-alpha1 will be released the week of July 16, 2018, which means new developments and disruptive changes should now be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.9.x-dev » 9.1.x-dev

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

catch’s picture

Category: Bug report » Task

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.