Problem/Motivation

Caching is disabled if an Ajax enabled form (constructed with the Webform module) is on the page. This is due to the webform module storing the user id of the currently logged in user in a value element (see webform_client_form()).

Steps to reproduce

Preconditions: Authcache including Authcache Debug installed and properly configured. Webform and Webform Ajax installed and enabled.

  1. Add a new Webform.
  2. Add any input field.
  3. In Form settinsg enable Ajax mode.

Verify that the following message is reported by the debug widget if an authcache enabled user (or anonymaus) accesses the form:

Value element contained in the cacheable form webform_client_form_X. Please enable a suitable Authcache integration module for that form or file a support

Proposed resolution

Write an integration module similar to Authcache Commerce capable of stripping the user-id value from the webform from within a form_alter and restoring it from within a validation callback prior to form submission. Then mark $form['details']['uid'] as cacheable. Also mark $form['details']['nid'] cacheable (no additional code is required in this case because the nid does not depend on the currently logged in user).

Remaining tasks

User interface changes

API changes

Original report

How do you set up caching for anonymous users? I can't get it to work.
This is my configuration for caching:

<?php

/* Redis configuration */
$conf['redis_client_interface'] = 'PhpRedis'; // Can be "Predis".
$conf['redis_client_host']      = '****';  // Your Redis instance hostname
$conf['redis_client_port']      = ****;
$conf['lock_inc']               = 'sites/all/modules/redis/redis.lock.inc';
$conf['path_inc']               = 'sites/all/modules/redis/redis.path.inc';
$conf['cache_backends'][]       = 'sites/all/modules/redis/redis.autoload.inc';
$conf['cache_default_class']    = 'Redis_Cache';
$conf['redis_client_password']  = '****';

/* Cacheobject configuration */
$conf['cache_backends'][] = 'sites/all/modules/cacheobject/cacheobject.inc';
$conf['cache_class_cache_form'] = 'CacheObjectAPIWrapper';
$conf['cacheobject_class_cache_form'] = 'DrupalDatabaseCache';

/* Authcache configuration */
$conf['cache_class_cache_authcache_key'] = 'Redis_Cache';
$conf['page_compression'] = 1;
$conf['page_cache_maximum_age'] = 600;
/* The following needs to be disabled for Varnish */
# $conf['authcache_builtin_cache_without_database'] = TRUE;
# $conf['cache_backends'][] = 'sites/all/modules/authcache/authcache.cache.inc';
# $conf['cache_backends'][] = 'sites/all/modules/authcache/modules/authcache_bu$

/* Varnish configuration */
$conf['cache_backends'][] = 'sites/all/modules/varnish/varnish.cache.inc';
$conf['cache_class_cache_page'] = 'VarnishCache';
$conf['page_cache_invoke_hooks'] = FALSE;
$conf['omit_vary_cookie'] = TRUE;


?>

Comments

znerol’s picture

Are you trying to use varnish for caching? Is authcache working for authenticated users and not for anonymous users? Or is it not working at all? Did you follow a specific guide / blog post in order to reach the point you are stuck right now? If yes, then please post links pointing to that resources.

znerol’s picture

Status: Active » Postponed (maintainer needs more info)
stevendeleus’s picture

Version: 7.x-2.0-beta4 » 7.x-2.0-beta5
Status: Postponed (maintainer needs more info) » Active

Hi, thank you for helping me out. For integration with Redis I used https://www.drupal.org/node/2160133 . The connection with Redis is working and recognized by authcache. For integration of authcache form, I followed the readme of the cacheobject module. I disabled everything Varnish related and the problem persists.

I really can't get basic setup working. Drupal cache always misses. As far as I can tell the problem is with authcache for which page cache has to be disabled. The only reason I need authcache is for forms, to clear the unnecessary tokens that cause page caching to be disabled for sure.

What I think is happening, is that the check for page caching caused by form tokens happens before authcache clears them.

stevendeleus’s picture

The culprit is the webform_ajax module. I think it alters the form after authcache removes the token, causing the token to be renewed.

stevendeleus’s picture

Changing the order of the modules doesn't help or I'm doing it wrong.

stevendeleus’s picture

FYI:

<?php
function webform_ajax_form_webform_client_form_alter(&$form, $form_state, $form_id) {
  $webform = $form['#node']->webform;
  if ($webform['webform_ajax'] != WEBFORM_AJAX_NO_AJAX) {

    // The wrapper ID will have to follow the webform all the time, to be unique,
    // and allow AJAX commands to perform correctly.
    // We have many ways to get it back depending on the situation.
    $form['webform_ajax_wrapper_id'] = array(
      '#type' => 'hidden',
    );
    if (isset($form_state['values']['webform_ajax_wrapper_id'])) {
      $form['webform_ajax_wrapper_id']['#value'] = $form_state['values']['webform_ajax_wrapper_id'];
    }
    elseif (isset($form['#node']->webform['webform_ajax_wrapper_id'])) {
      $form['webform_ajax_wrapper_id']['#value'] = $form['#node']->webform['webform_ajax_wrapper_id'];
    }
    else {
      // At last, generate a unique ID.
      $form['webform_ajax_wrapper_id']['#value'] = drupal_html_id('webform-ajax-wrapper-' . $form['#node']->nid);
    }

    $form['#prefix'] = '<div id="' . $form['webform_ajax_wrapper_id']['#value'] . '">' . (isset($form['#prefix']) ? $form['#prefix'] : '');
    $form['#suffix'] = (isset($form['#suffix']) ? $form['#suffix'] : '') . '</div>';

    foreach (array('previous', 'next', 'submit', 'draft') as $button) {
      if (isset($form['actions'][$button])) {
        // vectorbross comment: the command below causes the form build id to be (re?)added
        $form['actions'][$button]['#ajax'] = array(
          'callback' => 'webform_ajax_callback',
          'wrapper' => $form['webform_ajax_wrapper_id']['#value'],
          'progress' => array(
            'message' => '',
            'type' => 'throbber',
          ),
        );
        if (in_array($button, array('next', 'submit'))) {
          $form['actions'][$button]['#ajax']['event'] = 'click';
        }
        // Workaround for Drupal core bug http://drupal.org/node/1548976.
        // As long as buttons HTML id are causing problems, and it has bad
        // consequences on pages where Webform AJAX is active, we'll force
        // custom ids on AJAXed Webform's buttons.
        $submit_id = drupal_html_id('edit-webform-ajax-' . $button . '-' .$form['#node']->nid);
        $form['actions'][$button]['#attributes']['id'] = $submit_id;
        $form['actions'][$button]['#id'] = $submit_id;
      }
    }
  }
}
?>
znerol’s picture

I guess that may be a similar problem like #2389809: Content load by authcache, and pager with ajax loading content. In that case it would be enough to force the $form_state['values']['webform_ajax_wrapper_id'] to a constant value from within a custom module.

znerol’s picture

Oh, stupid me. 7.x-2.0-beta5 does not cache any form which as a value element in it by default. However, the value element here does not contain any user-specific data and can safely be cached. It can be marked as cacheable from within a custom form alter callback like so:

function MYMODULE_form_webform_client_form_alter($form, &$form_state) {
  authcache_element_set_cacheable($form['webform_ajax_wrapper_id']);
}
stevendeleus’s picture

Hi, that doesn't help. I tried authcache_element_set_cacheable on webform_ajax_wrapper_id, but the form is not cached. Could I do something more generic to force the cache?

stevendeleus’s picture

I get this message from authcache_ensure_element_cacheable:
Value element contained in the cacheable form webform_client_form_8. Please enable a suitable Authcache integration module for that form or file a support request.
When I print the element I get:

(
    [#type] =&gt; value
    [#value] =&gt; 8
    [#post_render] =&gt; Array
        (
            [0] =&gt; authcache_ensure_element_cacheable
        )

    [#authcache_cancel_message] =&gt; Value element contained in the cacheable form <em class="placeholder">webform_client_form_8</em>. Please enable a suitable Authcache integration module for that form or file a support request.
    [#authcache_cancel_strict] =&gt; 1
    [#input] =&gt; 1
    [#process] =&gt; Array
        (
            [0] =&gt; _bootstrap_process_element
            [1] =&gt; _bootstrap_process_input
        )

    [#defaults_loaded] =&gt; 1
    [#tree] =&gt; 1
    [#parents] =&gt; Array
        (
            [0] =&gt; details
            [1] =&gt; nid
        )

    [#array_parents] =&gt; Array
        (
            [0] =&gt; details
            [1] =&gt; nid
        )

    [#weight] =&gt; 0
    [#processed] =&gt; 1
    [#required] =&gt; 
    [#attributes] =&gt; Array
        (
        )

    [#title_display] =&gt; before
    [#id] =&gt; edit-details-nid
    [#name] =&gt; details[nid]
    [#sorted] =&gt; 1
    [#children] =&gt; 
)
stevendeleus’s picture

I found two more elements that needed to be made cacheable, but the problem still persists.

stevendeleus’s picture

I found the last issue. The form needs to be passed by reference:

<?php
function MYMODULE_form_webform_client_form_alter(&$form, &$form_state) {
  authcache_element_set_cacheable($form['webform_ajax_wrapper_id']);
}
?>
stevendeleus’s picture

Status: Active » Closed (fixed)
znerol’s picture

I found the last issue. The form needs to be passed by reference

Oh sure! Sorry about that. Is #12 your complete implementation?

stevendeleus’s picture

No, this is:

<?php
function MYMODULE_form_webform_client_form_alter(&$form, &$form_state) {
	authcache_element_set_cacheable($form['webform_ajax_wrapper_id']);
	authcache_element_set_cacheable($form['details']['nid']);
	authcache_element_set_cacheable($form['details']['uid']);
	authcache_element_set_cacheable($form['submitted']['pagina']);
}
?>
znerol’s picture

Status: Closed (fixed) » Needs work
    authcache_element_set_cacheable($form['details']['uid']);

I'm sorry but that is very likely not safe and could lead to weird issues like the commerce cart one fixed in beta5. Take a look at the new Authcache Commerce module to get an idea on how to restore the actual user id from within a validation callback.

znerol’s picture

Title: anonymous user not cached » Support Ajax Webforms
Version: 7.x-2.0-beta5 » 7.x-2.x-dev
Category: Support request » Feature request
Issue summary: View changes

Add a proper issue summary and convert this to a feature request. Patches greatly appreciated.

znerol’s picture

Issue summary: View changes
jalike’s picture

Status: Needs work » Needs review
StatusFileSize
new6.43 KB

See proposed solution attached.

firewaller’s picture

I had some trouble debugging the value elements to get this working, so I passed the element key into the warning message to figure out where to look.

Status: Needs review » Needs work

The last submitted patch, 20: authcache-element-value-debug-2460953.patch, failed testing.

The last submitted patch, 20: authcache-element-value-debug-2460953.patch, failed testing.

The last submitted patch, 20: authcache-element-value-debug-2460953.patch, failed testing.

The last submitted patch, 20: authcache-element-value-debug-2460953.patch, failed testing.

The last submitted patch, 20: authcache-element-value-debug-2460953.patch, failed testing.

angel.angelio’s picture

Is authcache_element_set_cacheable() documented anywhere? I saw the same 'error'

Value element contained in the cacheable form X. Please enable a suitable Authcache integration module for that form or file a support

And the doctype was not intuitive. I used it because I found this issue.

/**
* Mark a render-element as cacheable.
*/

In my case the error was created from a custom form

znerol’s picture

Developer documentation is truly lacking. The only additional hint you get from the enclosing group heading:

/**
 * @defgroup authcache_markup Remove personalization
 * @{
 * Detect personalized content and remove it or cancel caching.
 */

Authcache tries to detect if it is save to cache the rendered markup + state of a form. Regrettably many forms assume that pages are never cached for authenticated users and therefore add some personalized bits here and there to some form elements. Some things are easy to spot when test driving the site manually, e.g. default values (name and email of the user prepopulated in the contact form). Other things are nearly impossible to detect, among them personalized form values (i.e., elements with type => '#value'). That did lead to weird behavior in the past (#2307555: Comments intermittently misattributed and #2475503: Ajax add to cart works only for initial user).

For this reason Authcache flat out refuses to cache a form if there is a value form element on the page unless some contrib/custom code did inspect the element and marked it as cacheable (using authcache_element_set_cacheable()).

Some value form elements do not contain any personalized content, but many do. If you use authcache_element_set_cacheable() on a value element which contains personalized content, but do not add additional steps which cope with that situation, then you will likely run into issues like those linked above.

angel.angelio’s picture

Thanks for the amazing explanation.