Steps to reproduce

  1. An ajax-enabled form that's accessible for the anonymous user. -- in my case a multi-page webform with webform_ajax enabled.
  2. Some custom theme is displayed. -- in my case by a module that sets it via hook_custom_theme()
  3. Either the page cache is enabled or the browser doesn't accept cookies (leading to the session_id() changing between the original form generation and the ajax request).

Result

The ajax request is handled using the default theme which means the styles for the default theme are loaded additionally to the styles of the custom theme (at least with less enabled) - but at the very least use the wrong template files.

What happens exactly?

ajax_base_page_theme() is responsible for setting the same theme that was used in the original form generation. It uses drupal_valid_token() to check whether the the user is allowed to set the theme via $_POST['ajax_page_state']['theme']. drupal_valid_token() uses the session_id() to check the token and thus fails (because the session_id() has changed).

In a broader picture drupal_valid_token() can't work reliably for anonymous in general in this form, because it relies on the session_id().

Proposed solution

Use another token generation/verification method that uses the form_build_id instead of the session_id().

Comments

torotil’s picture

Issue summary: View changes
pirog’s picture

torotil’s picture

It seems that #1334818: #ajax does not work in install profiles provides a installation specific workaround for the bug mentioned in this report. The other report seems to be another symptom of the same problem.

pirog’s picture

It would be great to fix the bug reported here so that #1334818: #ajax does not work in install profiles presumably does not need to.

mkalkbrenner’s picture

FYI
You can probably use ThemeKey as a workaround by setting up a simple "catch all" rule like system:dummy = dummy >>> YOUR_THEME and activating the "Bypass" option at /admin/config/user-interface/themekey/settings/ajax

corbacho’s picture

This bug also shows when:
* using (webform ajax or custom form with ajax) + domains module (domain_theme submodule)

Because domains_theme uses domain_theme_custom_theme
Someone reported this bug 3 years ago: https://www.drupal.org/node/1319344

jtwalters’s picture

I also ran into this issue using hook_custom_theme and there's a couple bugs that prevented it from working for me:

1. Page cache and ajaxPageState['theme_token'] — incompatible
2. session_id() changing on every page load — breaks token check

My workaround solution, at the top of my hook_custom_theme:

  // Hack: ensure ajax_base_page_theme avoids a token check since that does not seem to work reliably.
  if (strpos(current_path(), 'ajax') !== FALSE && isset($_POST['ajax_page_state']['theme'])) {
    $GLOBALS['conf']['theme_default'] = $_POST['ajax_page_state']['theme'];
    return FALSE;
  }
geek-merlin’s picture

Title: page cache breaks ajax_base_page_theme() » Page cache breaks nondefault ajax theme on GET
Version: 7.x-dev » 8.8.x-dev
Issue tags: +Needs issue rescope

Coming from #3075068: Make our ajax popups cacheable and #2177975: Page cache breaks nondefault ajax theme on GET.
POST requests should not be cached in the first place and i don't know if this still applies for D7, but it should not for D8.
But if we want to use GET for ajax for better cacheability, this should still be an issue. (Thus adding rescope tag and moving to D8 for now.)
So the additional conditions are
* use GET for ajax
* request a nondefault theme (see ajax_base_page_theme)
I'm not deep in the internals, but if we can add the session key/hash to the get parameters, we should be done. (Just FTR, i think i will not work on it.)

geek-merlin’s picture

Status: Active » Postponed (maintainer needs more info)

> I'm not deep in the internals, but if we can add the session key/hash to the get parameters, we should be done. (Just FTR, i think i will not work on it.)

Sorry, i was confused. The theme token is of course a session hash so i guess this should not be an issue anymore. Correct me if this manifests differently.

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.

pameeela’s picture

@geek-merlin this is no longer an issue in D8 but probably is in D7, yes? If so we can just update the ticket to be against 7.x rather than closing it.

geek-merlin’s picture

Version: 9.1.x-dev » 7.x-dev
Status: Postponed (maintainer needs more info) » Active
Issue tags: -Needs issue rescope

@pameeela: Yup, thanks!

pameeela’s picture

Issue tags: +Bug Smash Initiative
pamelalies’s picture

Thanks to @jtwalters, your fix in #7 solved my issue as well!

In my case I was using a View with Ajax, and after clicking to the second and subsequent pages of results, the theme reverted from the current correct theme to the default theme for anonymous users. Adding your fix in #7 at the top of my hook_custom_theme() resolved it.

In case seeing the full hook implementation will help anyone else, I'll paste it below. I added a pretty lengthy explanation of the fix to the code since we have multiple developers.

/**
 * Implements hook hook_custom_theme()
 */
function philanthropy_theme_custom_theme() {
  
  // If Ajax is in use, ensure ajax_base_page_theme() avoids a token check since 
  // that will revert to the default theme being used for anonymous users. The token 
  // check uses the user's session ID, and this ID changes between the original View 
  // generation and any ajax request for anonymous users. ajax_base_page_theme() 
  // is responsible for setting the same theme that was used in the original View 
  // generation. The function uses drupal_valid_token() to check whether the user 
  // is allowed to set the theme via $_POST['ajax_page_state']['theme']. 
  // drupal_valid_token() uses the session_id() to check the token and thus fails 
  // (because the session_id() has changed) resulting in erroneously loading the 
  // default theme. See: https://www.drupal.org/node/2177975.
  if (strpos(current_path(), 'ajax') !== FALSE && isset($_POST['ajax_page_state']['theme'])) {
    $GLOBALS['conf']['theme_default'] = $_POST['ajax_page_state']['theme'];
    return FALSE;
  }
  
  $current_page_path = explode('/', request_path());
  if ($current_page_path[0] === "philanthropy"
    || $current_page_path[0] === "philanthropy-home") {
    return 'philanthropy';
  }
}

Status: Active » Closed (outdated)

Automatically closed because Drupal 7 security and bugfix support has ended as of 5 January 2025. If the issue verifiably applies to later versions, please reopen with details and update the version.