I've got a setup with authcache and locale cookie (both latest dev).

I have configured language selection in the order session - cookie - browser - default

I've added a authcache property:

/**
 * Implements hook_authcache_key_properties_alter().
 */
function cst_cache_crawler_authcache_key_properties_alter(&$properties) {
  global $language;
  $properties['lang'] = $language->language;
}

It seems that authcache does not respect the $conf['page_cache_invoke_hooks'] = TRUE; in the settings.php which is adviced from locale_cookie. If i switch the language with the module lang_dropdown the ?language=en get paramenter gets appended to the url, the cookie get set for anonymous users. But authcache does not register it and skips the processing so the locale module will not know the new language and my authcache property does not switch.
For rebuilding the problem here a step by step:

  1. Clear all caches
  2. Visit front page in all languages to warm the cache
  3. switch the language to another than your current
  4. remove the get parameter "language" from the url
  5. now you get a cache hit and the language is not determined by cookie or session
  6. fallback / default language is going to be served from cache

The only workarround for this for now is to create sessions for every anonymous user to store the language in it. But this have to be tested.

For authenticated users there is another behaviour because they have a session where the language will get stored in so the cookie is not relevant for languge selection.

CommentFileSizeAuthor
#15 htaccess-lang.patch1.7 KBznerol
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

fox_01’s picture

Issue summary: View changes

It seems that there is the same behavior if i save the language information in a cookie for anonymous users. Every time the page is getting served from cache the browser / default language will show up. If i switch to a non-cached page the language automatically switches to the right one.

znerol’s picture

$conf['page_cache_invoke_hooks'] = TRUE; indeed is ignored by Authcache. However, it is possible to influence the way the authcache-key is calculated for anonymous users. The standard implementation simply uses $base_root as the authcache key for anonymous users. You can override that using the authcache_key_generator variable by supplying a callable via settings.php like this:

$conf['authcache_key_generator'] = 'my_key_generator';

function my_key_generator() {
  global $base_root;
  $variant = get_sanitized_variant_from_cookie($_COOKIE['lang']);
  return $base_root . $variant;
}

Be sure to thoroughly sanitize the cookie value before concatenating with the $base_root. You may just compare it to a whitelist of allowed values for example or use a switch statement.

znerol’s picture

Category: Bug report » Support request
Status: Active » Fixed

Linked this issue from the documentation. I guess this is answered, otherwise reopen.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

chanac_hares’s picture

Hello,

Firstly , Sorry for my bad english .

I re-open this bug , because i use authcache for anomymous users and language_cookie module.

in my settings.php i have

$conf['authcache_key_generator'] = 'test_authcache_key_generator';
/**
 * Cache key generator custom pour la gestion du cookie language en anonyme.
 */
function test_authcache_key_generator() {
  global $base_root;

  switch ($_COOKIE['cookie_language']) {
    case 'fr':
      $variant = '/fr';
      break;

    case 'en':
      $variant = '/en';
      break;

    default:
      $variant = '';
      break;
  }

  return $base_root . $variant;
}

But my problem is the folowing,

I visite mysite.fr -> My cookie_language = 'fr' because french is the default language
I visite mysite.fr/en -> My cookie_language = 'en' OK
I back to mysite.fr -> My cookie_language = 'fr' OK
I back to mysite.fr/en -> My cookie stay 'fr' KO

Like if after 3 changes authcache not change cookie_language ever ... Somebody can help me ?

chanac_hares’s picture

Status: Closed (fixed) » Active

Please see #5 for know why i re-open the bug .
Thx

znerol’s picture

@chanac_hares Does your setup work correctly without Authcache? Also note that the OP apparently uses Locale Cookie and not Language Cookie.

chanac_hares’s picture

@znerol,

Locale Cookie and Language Cookie. seems to be exactly the same, except the name.
Moreover, I activate authcache only for anonymous user, thus when i use an authentificated user or another role my cookie change itself correctly.

But, in your previous post #2 , you say

$conf['page_cache_invoke_hooks'] = TRUE; indeed is ignored by Authcache.

, maybe if hook are badly invoked in authcache , is for that that my cookie does not change correctly.

Have you another idea ?

znerol’s picture

Ok, I've read through the code of Language Cookie and it indeed looks like it is incompatible with Authcache due to its reliance on hook_boot.

But frankly I do not understand at all the use-case it solves in this particular case. Especially I do not understand the reason to combine cookie based language negotiation with URL based language negotiation.

My question is: If you have different URLs for each language of your site (which is good also for SEO), what is the reason to additionally take into account a cookie value? I.e. mysite.fr and mysite.fr/en are already separate entries in the cache due to the different URLs. What purpose is the cookie serving?

chanac_hares’s picture

Arf, there is no solution for me ...
i combine cookie language negociation ans base url negociation, for solve the following use case :

  • I'am an english user et and visite the mysite.fr for the first time
  • I get the french home page (My cookie language swith to 'french')
  • I understand nothing , so i click on the english flag, i switch the language and i'am automatcly redirected to mysite.fr/en
  • I get the english home page (very goog !) (My cookie language swith to 'english')
  • But if two day later, i go back to mysite.fr i would like get the english home page on this url , BUT the authcache cache for this url is the french cache ( bad :!:)

Do you think it's a strange use case ?

znerol’s picture

@chanac_hares: Thanks for the explanation. It indeed makes sens to store the last used language in the users browser. However the way the mentioned modules are implemented is inherently incompatible with authcache (and also with external caches).

Still I think this is solvable if the language cookie would be set using JavaScript in the client instead of in hook_boot() on the server. I will try to explore that solution as time permits.

znerol’s picture

I thought about the problem a bit more and this is what I recommend to owners of multilingual sites wishing to use page level caching (including Varnish, Boost and Authcache):

Exclusively use URL based language negotiation (either with subdomains or path prefixes). Do not enable any language negotiation method which depends on request properties which cannot be encoded in the URL such as browser (Accept-Language header) or Cookie. Otherwise there is a risk that content in different languages is mixed up when storing/delivering cached pages.

Use your web-server or gateway cache to redirect first-time users hitting your front-page to the page in the proper language (e.g. by inspecting the Accept-Language header). This could be done with mod_rewrite (Apache), or a VCL snipped (Varnish).

In order to address the use case outlined in #10, i.e. remember the language for a returning user, set a cookie in the users browser with the interface language of the currently viewed page - but use a simple JavaScript implementation instead of trying to set it in hook_boot or hook_init.

The code can be as simple as this:

/**
 * Implements hook_page_build().
 */
function mymodule_page_build(&$page) {
  global $language;

  drupal_add_js(array(
    'mymodule' => array(
      'name' => 'lang',
      'value' => $language->language,
      'options' => array(
        'expires' => 30, // days
        'path' => ini_get('session.cookie_path'),
        'domain' => ini_get('session.cookie_domain'),
      ),
    ),
  ), 'setting');

  drupal_add_js(drupal_get_path('module', 'mymodule') . '/mymodule.js');
  drupal_add_library('system', 'jquery.cookie');
}
(function ($, Drupal) {
  "use strict";

  Drupal.behaviors.mymodule = {
    attach: function (context, settings) {
      var cookie = settings.mymodule;
      $.cookie(cookie.name, cookie.value, cookie.options);
    }
  };

}(jQuery, Drupal));

This approach can be reused to also store other site-preferences, e.g. consumer vs business division, country/branch office for multinational sites. Obviously this does not work with user-specific data - the cookie properties will be cached along with the page.

Please post snippets of your mod_rewrite, VCL, Nginx config here if you've successfully implemented this approach.

chanac_hares’s picture

OKay thank you for your answer, and for have take the time to think to my problem !
Your hook_page_build() + js code work like a charm, now my cookie change each time i switch the langage.

Now i have two new problem, but maybe i'am missing something (very possible !) ..

I have set up your hook + js code, and i add the folowing code in my settings.php for recalculate the authcache_key

$conf['authcache_key_generator'] = 'test_authcache_key_generator';
/**
 * Cache key generator custom pour la gestion du cookie language en anonyme.
 */
function test_authcache_key_generator() {
  global $base_root;

  switch ($_COOKIE['cookie_language']) {
    case 'fr':
      $variant = '/fra';
      break;

    case 'en':
      $variant = '/eng';
      break;

    default:
      $variant = '';
      break;
  }

  return $base_root . $variant;
}

It doesnt work, snif ...

I have two more question and some tracks digging:

  1. Are you sure that the language cookie via js is updated before the authcache key is calculated ? It seems to be not but i have not yet find where exactly this hook is called
  2. Activating authcache_debug, when i visiting my.site.fr/fr (french website site part) i have an Authcache Key like : "http://mysite.fr/fra" ( OK my new generator key is well called) BUT i have an Cache CID: "http://mysite.fr/fra/fr" and i missed my cache !!

I cant dig deeper for today, i will continue tomorow, But i you have some advises, tracks or tricks i'am your man !

Thx in advance

znerol’s picture

The lang cookie is set when the browser renders the page. This is obviously after it was built by Drupal and after it has been requested by the browser.

With the method outlined in #12 you should not use authcache_key_generator anymore. Instead you need to set up the following structure:

  1. http://mysite.com/ no content, only redirects to /fr or /en based on the cookie
  2. http://mysite.com/fr/ french site
  3. http://mysite.com/en/ english site

It is not possible to implement the redirection in PHP (at least if you do not want to hack index.php), instead you need to find a way to trigger the redirection by your webserver (presumably Apache). I recommend to search the web for something like mod_rewrite redirect based on a cookie value.

znerol’s picture

FileSize
1.7 KB

I propose the following mod_rewrite scheme. Consider a site with the following languages enabled (ordered by site preference): Dutch (default), French, English.

  1. Navigate to Administration » Configuration » Regional and language » Languages and move the languages into the proper order, select default for the uppermost.
  2. Edit each language one at a time and make sure that the Path prefix language code is set to an appropriate value.
  3. Navigate to the Detection and Selection tab, ensure that only URL is enabled.

Edit .htaccess and scroll down to the RewriteBase statement. All rewrite rules should be inserted below that line.

Start with initializing a new variable lang with the default language:

  RewriteRule ^ - [E=lang:nl]

If you wish to detect the language in the users browser, add the following condition/rule pair for every language from low to high priority (i.e. if your site is best viewed in Dutch, second best in French and third in English, then first insert a rule for en, then one for fr and finally one for nl.

  RewriteCond %{HTTP:Accept-Language} en
  RewriteRule ^ - [E=lang:en]

If you wish to detect the language from the lang cookie, add the following condition/rule pair for every language from low to high priority (i.e. if your site is best viewed in Dutch, second best in French and third in English, then first insert a rule for en, then one for fr and finally one for nl.

  RewriteCond %{HTTP_COOKIE} lang=en
  RewriteRule ^ - [E=lang:en]

Finally we need one condition/rule pair to redirect users hitting the front-page:

  RewriteCond %{REQUEST_URI} ^/*$
  RewriteRule ^/*(.*)$ /%{ENV:lang}/$1 [L,R=302]

And optionally we can also redirect every page without a language prefix, unless it is a file on the disk:

  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteCond %{REQUEST_FILENAME} !-d
  RewriteCond %{REQUEST_URI} !^/*(nl|en|fr)/?
  RewriteRule ^/*(.*)$ /%{ENV:lang}/$1 [L,R=302]

The attached patch shows the whole example ready for inclusion into a .htaccess file.

chanac_hares’s picture

Hello znerol,

Thx for your eplanation, I think we are closed.
But your solution seems doesnt work with the htaccess below ( definitively is my fault !! ) , because of the two following point :

  • RewriteCond %{HTTP_COOKIE} language_detection=en doesnt work, i have my cookie well configured but i never pass in the the rewrite rules like if the condition doesnt match ...
  • I always redirect to mysite.fr/fr BUT my front page is considered like a 404 error page and the cache doesnt work

My htaccess is :


            RewriteEngine on
            RewriteBase /

            RewriteRule "(^|/)\." - [F]

            // My default language is French
            RewriteRule ^ - [E=lang:fr]

            // If my cookie is set i change my language
            RewriteCond %{HTTP_COOKIE} language_detection=en
            RewriteRule ^ - [E=lang:en]
            
            // I redirect my front page
            RewriteCond %{REQUEST_URI} ^/*$
            RewriteRule ^.*$ /%{ENV:lang} [L,R=302]
            
            // For clean url i presume ? 
            RewriteCond %{REQUEST_FILENAME} !-f
            RewriteCond %{REQUEST_FILENAME} !-d
            RewriteCond %{REQUEST_URI} !=/favicon.ico
            RewriteCond %{REQUEST_URI} !^/en$ // ADD for prevent a loop
            RewriteCond %{REQUEST_URI} !^/fr$ // ADD for prevent a loop
            RewriteRule ^(.*)$ index.php?q=$1 [QSA,L]

any idea ?

znerol’s picture

Please take a close look at the patch file I posted in #15 and take that as the basis of your work.

chanac_hares’s picture

Hello znerol,

Finally i improve my first solution until the following result :

 /**
 * Authcache key generator to deal with cookie language and persistent login
 */
function gestion_des_caches_cookiebasedkey() {
  global $base_root;

  $path = request_path();
  $args = explode('/', $path);
  $prefix = array_shift($args);

  $cookie_name = variable_get('language_cookie_param', 'language');
  // test if path is coherent with cookie (path is prioritary)
  if (isset($prefix) && ($prefix == "en" || $prefix == "fr") && (!isset($_COOKIE[$cookie_name]) || $prefix != $_COOKIE[$cookie_name])) {
    language_cookie_set($prefix);
    $key = $prefix;
  }
  // if no path info and cookie, use cookie info
  elseif (isset($_COOKIE[$cookie_name])) {
    $key = $_COOKIE[$cookie_name];
  }
  // if no path info and no cookie, search with default language
  else {
    $key = 'fr';
  }

  // if persistent login don't use cahe
  $cookie_name = variable_get('persistent_login_cookie_prefix', 'PERSISTENT_LOGIN_');
  foreach ($_COOKIE as $k => $v) {
    if (strpos($k, $cookie_name) !== FALSE) {
      $key = uniqid() . $key;
      break;
    }
  }
  return $key . $base_root;
}

The probleme of compliance between cookie_language and authcache is resolved by changing the cookie into the key calculating function itself ..

Now every thing is OK . I add a deal whith persistent login too .

Thx for your precious help .

znerol’s picture

Status: Active » Fixed

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.