0

I'm looking for a way to make changes in rendered HTML parts, when they are the result of an AJAX request, just before they are returned to the user.

Currently it concerns Views, but its own hooks don't meet my need since the changes I want to make don't regard Views results but Views admin responses.
Anyway I'd like to be as generic as possible, so I could intervene upon any AJAX return.

Thanks in advance for any idea.


Issue when using the interesting solution proposed by Andre-B

It seemed to perfectly meet my need so I introduced it gradually into my module in order to test successively each of its aspects.

First I added only a simple trace to my module, using FirePHP:

<?php
function mymodule_init() {
  if (!empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') {
    $buffered_output = &drupal_static('mymodule_output_buffered', true);
    // AJAX request
    fb($buffered_output,'init');
  }
}

function mymodule_exit() {
  $buffered_output = &drupal_static('mymodule_output_buffered', false);
  if ($buffered_output) {
    fb($buffered_output,'exit');
  }
}

The results were:

  1. no trace when AJAX not involved
  2. for an AJAX request, the trace was:

    • init: 1
    • exit: 1

In other words, it worked as expected, without any error message and normally delivering the HTML output.

Then, in order to now control what was uncertain about the warning stated in the hook_exit page:

This hook MUST NOT print anything because by the time it runs the response is already sent to the browser.

I simply changed for the following:

<?php
function mymodule_init() {
  if (!empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') {
    $buffered_output = &drupal_static('mymodule_output_buffered', true);
    // AJAX request
    ob_start();
  }
}

function mymodule_exit() {
  $buffered_output = &drupal_static('mymodule_output_buffered', false);
  if ($buffered_output) {
    print ob_get_clean();
  }
}

What I expected was, either:

  • if there was a real issue regarding the warning, it should merely NOT return the expected HTML output
  • otherwise all should have worked as usual

But in fact I got the error message:

Call to undefined function MYFUNCTION (which belongs to MYMODULE, see below)...

At the opposite of what was expected, this relies to the OTHER warning stated by the hook_exit page:

If you implement this hook and see an error like 'Call to undefined function', it is likely that you are depending on the presence of a module which has not been loaded yet. It is not loaded because Drupal is still in bootstrap mode.

At this stage, I don't understand what happens:

  1. At a first view, seems that introducing ob_start() and ob_get_clean() should not change anything to which modules are already loaded or not. In other words, if it fails here, it should have failed with the previous test too
  2. Secondly, the allegedly undefined function (say undef_func()) belongs to my module: it resides in an additional file, which is loaded by mymodule.module (using module_load_include()), while the hook_exit() function is also located in mymodule.module, so undef_func() should have already been loaded when hook_exit() is called

To be more clear, here is (part of) the mymodule schema:

file: mymodule.module

<?php

module_load_include('inc', 'mymodule', 'mymodule.data');
module_load_include('inc', 'mymodule', 'mymodule.core');

function mymodule_init() {
# ...
}

function mymodule_exit() {
# ...
}

# other mymodule hooks...

file: mymodule.core

<?php

function undef_func() {
# ...
}

# other mymodule core funcs...

For now I don't see how to understand what is happening...


Issue solved, but a mistery remains

I tested more, adding a lot of trace points to identify how and when the files were loaded.

Then I first realized that, since I was connected with administration menu active, every call to a mysite url caused successively:

  1. the entire page to be returned as expected
  2. then an AJAX request to be sent by admin_menu

And I discovered that, for some reason, this AJAX request only invokes hook_init(), while any non-AJAX call and all other AJAX requests invoke both hook_init() and hook_exit().
It was apparently the reason why I got the error mentioned above.

Another strange thing is that, systematically, after an error had been fired:

  1. flushing class registry caused a normal page to be rendered without any error
  2. then F5 fired the error again

And so on, cyclically, without any change in the code! I can't figure out how this can be explained.

In the other hand, once this behaviour known, I could act to avoid this case, and do the job where I need it.

So definitely, this solution works!

0

This is all dev stuff and belongs in a custom module. Basic idea behind this: buffer any output, modify it before it get's send out, send it out.

<?php
/**
 * Implements hook_init();
 */
function mymodule_init() {
  if (!empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') {
    $buffered_output = &drupal_static('mymodule_output_buffered', true);
    // AJAX request
    ob_start();
  }
}

// see https://api.drupal.org/api/drupal/modules!system!system.api.php/function/hook_exit/7
// it states that this module must not print anything, but it should be possible
// by looking at the code, you'll just have to make sure that you don't break the
// request and be aware that headers area already sent, and you have to catch the
// output in the first place if you intend to modify it.

/**
 * Implements hook_exit();
 */
function mymodule_exit() {
  $buffered_output = &drupal_static('mymodule_output_buffered', false);
  if ($buffered_output) {
    $ajax_response = ob_get_contents();
    ob_end_clean();
    // do your string manipulations here.
    $ajax_response = str_replace('something', 'intosomethingelse', $ajax_response);
    print $ajax_response;
  }
}

You can implement any other logic in your hook_init implementation to start buffering the output, even define additional options in other static variables that can be acted on in your hook_exit().

Be aware to keep the logic in your hook_init and hook_exit as lean as possible, since it is run on each request.

  • Thanks a lot for this response, exactly what I need. But I fell into a problem while testing it "empty" -that is, with only echo ob_get_clean() in mymodule_exit(). So should return all ok, like if I did nothing, isn't it? But I got a "Call to undefined function" message, as warned in the [hook_exit page] (api.drupal.org/api/drupal/modules!system!system.api.php/…), which also mentions that it is likely that you are depending on the presence of a module which has not been loaded yet. Very strange, since the missing function belongs to mymodule! I remain stump... – cFreed Apr 1 '15 at 21:13
  • you could try to load the functions manually: api.drupal.org/api/drupal/includes!module.inc/function/… – Andre Baumeier Apr 2 '15 at 0:57
  • I'm already using module_load_include(). Comment is a too little space to be clear, so I edited my question: thanks in advance to look at it. – cFreed Apr 2 '15 at 11:50
  • I finally could use your solution (look at my new edit above). I'm really sorry I don't own reputation enough to be allowed to +1. Thanks a lot. – cFreed Apr 3 '15 at 13:43
  • if it really is a bootstrapping issue (which might be) than you can bootstrap the current request to a certain level using drupal_bootstrap() - but this will probably slow down some ajax requests, so be aware of your changes and measure the performance. sometimes it helps to avoid bootstrapping and going directly to the db or whatever might be useful in certain environments, it is hard to give a general advice on this. – Andre Baumeier Apr 3 '15 at 17:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.