I have tested on 4 sites, 2 on a multisite setup, running PHP 5.2.5 on Apache 2.0.54, a single site running PHP 4.4.8 on Apache 2.0.54, both on Debian Linux with kernel 2.6.20.2, and also on Mac OS X 10.5.2 running MAMP 1.7 (Apache 2.0.59 & PHP 5.2.5). Each of these sites adds two characters to the CSS and Javascript includes in the head. The first character is always ?, and the second character is the same across a site, but different between sites (eg i have ?b, ?c, ?V, and ?T across the four sites). This is fine for most situations, except for when I am referencing a dynamically generated stats javascript (Mint - see http://haveamint.com/ - the default call is /mint/?js) , the extra ? causes the script to fail, meaning that the stats are not being tracked.

For examples, see the multisite installs at http://WhenTheTongueIsDone.net and http://health2life.co.nz, and for the standalone install which is simply a brand new install at http://WhenTheTongueIsDone.com/. View the source on each of these, and you will see the extra characters which have been attached to the <script> and CSS <link> tags.

Comments

zeta ζ’s picture

Title: Extra characters are attached to all Javascript and CSS inserts entered by Drupal » Admin option to disable dummy query-string appended to css and js file links
Category: bug » feature
Priority: Critical » Normal

These extra characters implement a very useful feature of Drupal 6, forcing a reload of these files when they have changed, gaining control over the browser cache.

See http://drupal.org/node/45111 for correct use of Priority critical.

Your issue is one of compatibility between Drupal and Mint: have you filed an issue with haveamint.com? Maybe they have an alternative to the default call.

I’ll leave it to someone else to decide if this is desirable.

wretched sinner - saved by grace’s picture

Status: Active » Closed (duplicate)
zach harkey’s picture

Category: feature » bug
Status: Closed (duplicate) » Active

Let me decide if this feature is useful or not. In my case it is less than useful. In fact, it's crippling. When I need to trick browser caches I can implement the browser query string trick easily at the theme level.

This is not just an issue between Drupal and Mint. This fake query string also wreaks havoc with certain live preview css editors features. For example, it makes CSSEdit's live preview feature utterly worthless.

The fact that there is no way to disable this feature is a big problem.

This issue was originally created for Version 6.1. IMO It should not be marked as a duplicate of a drupal 7 issue.

Setting this to an active Drupal 6 Bug.

It is a bug because it assumes things that it should not assume with regard to browser caching and allows not way to be disabled.

wretched sinner - saved by grace’s picture

Status: Active » Closed (duplicate)

@Zach

I had marked it as a duplicate of a bug that I came across subsequently, which is for D7 - #243251: JavaScript requiring a query string cannot be loaded. I suggest marking this back as a duplicate of this bug, and the discussion can continue there, as the original discussion there is about the WYSIWYG editor issue.

zach harkey’s picture

Title: Admin option to disable dummy query-string appended to css and js file links » No way to disable 'cachebuster' query-string appended to CSS file links.
Version: 6.1 » 6.x-dev
Status: Closed (duplicate) » Active

@Sinner, I tried to like that thread, and it is certainly related. However it is, and probably should remain, focused on drupal_add_js(), particularly as it relates to modules like wysiwyg editors. I have changed the title of this post to specifically address CSS.

Currently there remains no way to disable the cachebuster query string from the end of links to CSS files specified in a theme's .info file. This remains a bug that needs to be addressed.

wretched sinner - saved by grace’s picture

This issue was not created about CSS issues - it was about JS.

It might be better to start another issue for CSS, and link it to #243251: JavaScript requiring a query string cannot be loaded, as there is a large amount of work going on which is changing how both CSS and JS are handled, and they are looking to use a single API to acheive this.

See #251578: More flexible js/css ordering and an alter operation which links to the individual nodes for the ongoing work. They are discussing the caching of scripts and CSS there too.

wretched sinner - saved by grace’s picture

Status: Active » Closed (duplicate)
creslin’s picture

I concur - this is not useful at all.

First it's obfuscated, most developers who

* write file.css
* add file.css to theme.info

Expect file.css to be called from the browser - not file.css?[a-z]

Secondly this has silently bust our mod_deflate in Apache for all CSS's served - meaning 10x the bandwidth and latency implicit in downloading a larger file.

Third, content switching above Drupal hosts to pull css/media from a.another server is now bust - all content is being served from our dynamic host. I simply cannot configure our load Layer5 Load balancers to manage a ? in the GET to determine which server to get the data from.

How do I switch this madness off, what ever problem it was addressing seems very small delivery minded, has undermined enterprise delivery.

- Cres

Mixologic’s picture

@creslin: Nope. There is no way to disable this without a core hack. You could simply comment out line 1846 and line 2201, but I prefer the following method. Untested, but this should work for drupal 6.15:

In your settings.php file set a variable like so (or add it to your conf array if you are already setting other variables):

$conf = array(
'disable_css_js_dummy_querystring' => TRUE,
);

and hack your way into includes/common.inc.

Line 1846
if (!variable_get('disable_css_js_dummy_querystring', FALSE)){
$query_string = '?'. substr(variable_get('css_js_query_string', '0'), 0, 1);
}

Line 2201
if (!variable_get('disable_css_js_dummy_querystring', FALSE)){
$query_string = '?'. substr(variable_get('css_js_query_string', '0'), 0, 1);
}

If you've got enterprise delivery situation, then you of course have dev/staging/production environment where you only want this in your dev's settings.php.

KhaledBlah’s picture

@Mixologic, I can confirm that your idea does indeed work. I have added it just like you suggested. Thanks!

@all: I want to add a 4th reason: I work for a network security company and we use drupal only internally and then "export" the drupal site to static HTML and then transfer those static HTML files to our public webserver. This is obviously for security reasons because we cannot afford to have any hickups with our website and a dynamic systems however secure it is does add a bit of security risk. Adding this question mark to CSS/JS files may allow cache control but IMO it does not confirm to the idea of HTTP and it breaks a few viable features. So, I guess the user should able to disable it without a patch if he or she choses, too.

KhaledBlah’s picture

I have developed a solution that doesn't require a core hack. Simply put the code below in your template.php inside your theme directory and there e.g. in the phptemplate_preprocess_page($vars) function.

  $search = array('scripts' => 'src', 'styles' => 'href');

  foreach ( $search as $var => $word ) { 
    if ( !empty($vars[$var]) ) { 
      $lines = explode("\n", $vars[$var]);
      $result = array();
      foreach($lines as $line) {
        $matches = array();
        if ( preg_match('/' . $word . '="(.*)"/', $line, $matches) ) { 
          global $language;
          $match = $matches[1];
          $replacement = $matches[1];

          // remove the ? and everything behind it
          $pos = strpos($replacement, '?');
          $replaced = $line;
          if ( $pos !== FALSE ) {
            $replacement = substr($replacement, 0, $pos);
            $replaced = str_ireplace($match, $replacement, $line);
          }
          $result[] = $replaced;
        }
        else {
          $result[] = $line;
        }
      }   

      if ( !empty($result) ) { 
        $vars[$var] = implode("\n", $result);
      }   
    }   
  }
zach harkey’s picture

SWEET! Works perfectly. Thanks, KhaledBlah

clintfuhs’s picture

This seems to be exactly the solution I am looking for. But, we use @import to load css files. In the first array, what would I replace "href" with to make this work. I tried "@import" but it didn't work.

Thanks..

schen’s picture

Version: 6.x-dev » 7.0

What's the solution for Drupal 7? Any suggestions are welcomed.

gilcreque’s picture

Here is a solution for Drupal 7 and to change the css files brought in via @import. You have to put this on the hook_process_html(&$vars) function, which I had to create in my template.php file.

  $search = array('scripts' => 'src=', 'styles' => 'href=', 'styles' => '@import\surl\(');
  foreach ( $search as $var => $word ) {
    if ( !empty($vars[$var]) ) {
      $lines = explode("\n", $vars[$var]);
      $result = array();
      foreach($lines as $line) {
        $matches = array();
        if ( preg_match('/' . $word . '"(.*)"/', $line, $matches) ) {
          global $language;
          $match = $matches[1];
          $replacement = $matches[1];
          // remove the ? and everything behind it
          $pos = strpos($replacement, '?');
          $replaced = $line;
          if ( $pos !== FALSE ) {
            $replacement = substr($replacement, 0, $pos);
            $replaced = str_ireplace($match, $replacement, $line);
          }
          $result[] = $replaced;
        }
        else {
          $result[] = $line;
        }
      }   
      if ( !empty($result) ) {
        $vars[$var] = implode("\n", $result);
      }
    }
mohsenzahra’s picture

Issue summary: View changes

i know it for css but not js and images

function MYTHEME_process_html(&$variables) {
$variables['styles'] = preg_replace('/\.css\?.*"/','.css"', $variables['styles']);
}

after copying this file in template.php, clear all flush cache, if an error happes find function name MYTHEME_process_html in template.php file perhaps it`s duplicated. after finding the function, copy the line in that

Reuben Unruh’s picture

For the workflow where you often refresh your dev database from live and your settings.php is not in your git repo...

in settings.php of your dev site only:

$conf = array(
  'preprocess_css' => '0', // Not necessary to strip query strings but will make sure CSS isn't aggregated on dev site
  'preprocess_js' => '0', // Not necessary to strip query strings but will make sure js isn't aggregated on dev site
  'SOMETHING_UNIQUE_strip_query_strings' => TRUE,
);

and in template.php:

function MYTHEME_process_html(&$variables) {
  if (variable_get('SOMETHING_UNIQUE_strip_query_strings', FALSE) && $query_string = variable_get('css_js_query_string', FALSE)) {
    $variables['scripts'] = str_replace('?' . $query_string, '', $variables['scripts']);
    $variables['styles'] = str_replace('?' . $query_string, '', $variables['styles']);
  }
}

Be careful since if your SOMETHING_UNIQUE_strip_query_strings variable does not have a name that's unique enough, the query strings would be stripped on a live site.

Reuben Unruh’s picture

@myself stop trying so hard and just do this (in settings.php on a dev site only):

$conf = array(
  'css_js_query_string' => '',
);
davidwhthomas’s picture

@Reuben Unruh,

That code in #18 will reset / overwrite the $conf array variable.

If you want to set a blank css_js_query_string, better to use $conf['css_js_query_string'] = ''; on your dev environment settings.php.