I was trying to come up with a clean way to add a sitewide CSS file that would override all modules and the selected theme. I had started with http://www2.drupal.org/node/155400.

I wound up with a simple patch to drupal_get_css function in common.inc (see attached diff for Drupal 5.2).

It essentially allows CSS files to be added to the list using drupal_add_css as shown below in an example that could be in a module allowing it to be easily enabled or disabled.

function last_css_menu($may_cache) {
if (!$may_cache) {
drupal_add_css(drupal_get_path('module', 'last_css') .'/last_css.css', 'last', 'all', TRUE);
}
return array();
}

The 'last' parameter option is added to the existing 'module' and 'theme' with the new option placing any added CSS file references after the theme thereby allowing a CSS designer to override any module or theme related CSS settings.

I found this a better way to override multiple themes without having to modify the themes themselves.

Comments

imrook’s picture

While I think the general idea is pretty good I think the implementation needs more work. For example if two modules wanted to override a default drupal style and they both added their CSS files using 'last' which one is actually going to get applied? What if the user wants the other one last? The first thing that comes to my mind is adding weights. I think it would be good to have the opinion of some core developers as well. Anyone want to comment?

bwong’s picture

StatusFileSize
new4.02 KB

I agree that weights would be preferable. This was sort of a simple solution so that I could add CSS files that would occur after everything else.

As it turns out, adding weighted CSS files was trivial hence the new patch. I also added a 'first' set so the sequence for listing CSS files is now:

first, weight used for low to high sequencing, default weight is 0
module
theme
last

The weight parameter is optional so it should not affect existing uses of drupal_add_css, at least the ones I found when searching my installation. As far as I can tell, the static $css variable value that is returned is only used by drupal_get_css so changing the result structure should not cause a major problem.

dvessel’s picture

Version: 5.2 » 7.x-dev

New features will only be accepted for 7.x now.

JirkaRybka’s picture

I'm not sure - can you please explain some typical use case for this? It used to be theme CSS last, because IMO theme should be allowed to override everything. Drupal just provide defaults for things NOT defined by theme, which is often the case with special/modules custom items anyway. The CSS theming is a nightmare already for a not-very-experienced user (as I experienced myself more than year ago), Drupal defines so much, that overriding styles in theme is often a big try-and-error fight, seeking otherwise unnecessarily specific selectors etc., nasty in general in some cases... So I'm afraid that if the theme author can't even rely on his CSS coming last, it'll make things worse. There should be a pretty strong reason to add such a thing, and if so, it needs to be well documented, and used very sparingly.

bwong’s picture

Being able to arbitrarily set CSS file order lets the web designer control exactly what will have priority. I have a site that allows multiple themes but I don't want to have to edit every theme to make sure special fields are laid out correctly. For this particular instance, having a CSS file that comes after a theme means I can make one CSS file that handles any special fields the same way regardless of what theme is selected.

There is a caveat of course, since one theme may do things one way and another a different way. A general solution for handling a field may not be possible. On the other hand, if a theme is modified to include its name as a class in say the tag then it would be possible to have one CSS file that would handle all cases since it is trivial to add a class name to a definition.

One thing I didn't include was a straight forward way of determining what CSS files were in the list. This would make it possible to see what theme or modules were including CSS files and then a matching CSS file could be added in the proper order.

Likewise, for modules that use CSS files, it is useful to override these defaults but not in the theme. That means you want a position between the default module placement and the theme. This would be at the end of the module list or at the beginning of the theme list.

The multiple group/weighted approach is probably more general that it needs to be but it is simple and easy to understand. It also provides the kind of fine control that some web designers may need.

JirkaRybka’s picture

Hmm, I would rather just rely on CSS selectors priority (specificity). Your solution, not trivial code-wise, will only help if the CSS selectors are exactly the same in all involved themes, otherwise the CSS priority-model wins anyway.

Let's look at simple example. The HTML is:

<div class="wrapper">
  <ul>
    <li class="item">Some text</li>
  </ul>
</div>

If your theme's CSS comes last, and defines:
li.item { something; }
it may be still overriden inside any other CSS file by writing:
ul li.item { something; }
Or even more bullet-proof:
body div.wrapper ul li.item { something; }
If you have any id selector available, you can override almost everything - it's all just about your policy on selectors specificity across the themes. See http://www.w3.org/TR/CSS21/cascade.html#specificity for more. The order of CSS files is only just the lowest-priority factor out of 5, which may be overriden way tooo easy to be worth of creating patches IMO.

And the other way around - if you changed the order of CSS files, any of the involved themes will be still able to override your settings the same way as shown above.

So I would rather build the whole thing on CSS native priority-model, regardless the order of files.

JirkaRybka’s picture

Thinking of it, your use case (i.e. CSS rules meant to be some general-level overrides usable in more different documents / different themes) is an excellent candidate to use the CSS "!important" rules (no matter in which file/order located), which seems to me most elegant and clean solution.

hass’s picture

I have already tried to explain in D5 beta that we need a weight feature for CSS, but this request was refused.

An order like:
module
theme
last

makes no sense and doesn't solve the problem! Only solution that makes sense is a real weight, while you came in the situation that you need to keep a specific css order. Read this http://drupal.org/node/92877 and this http://drupal.org/node/92876. So after you read #92877 we must set this case to "by design" or reopen the older case.

hass’s picture

Additional for the reordering of files you can read this: http://drupal.org/node/173946#comment-303780

bwong’s picture

Just to clarify the priority issue. The order that I have implemented is really:

first <0
first =0
first >0
module <0
module =0
module >0
theme <0
theme =0
theme >0
last <0
last =0
last >0

All are useful and this order is useful even given the various override options since it is possible for any CSS to to be more specific until you have fully defined the context of a particular definition. In fact, I have overridden theme definitions in this fashion but the CSS file has to be after the theme file. Likewise, it is handy to put definitions between modules and themes (using module >0 or theme <0) to override a module like Panels while allowing customization by theme.

Regarding the module and theme override issues, I agree that this should be done sparingly and carefully but it is definitely better than editing the module or theme CSS files directly. In fact, I would like to make all modifications so they are independent of a theme or module. This allows easier upgrade of themes or modules since there has not been prior modification of code or CSS files.

I understand that there is a possibility that a change in an upgraded module or theme might cause problems or different display results but should now be easier to figure out the problem by simply disabling the customization CSS file.

I have been experimenting with the ability to use this priority mechanism to associate CSS files with modules and themes in addition to a global override (no patch code yet). This type of mechanism would make it easier to enable and disable these files.

If the best way to get this discussion restarted is to reopen http://drupal.org/node/92877 then I think we should do so. Regardless of the availability of other refinement methods, sequence priority remains on of them and we should have the ability to take advantage of it without going to extremes like modifying module or theme code.

hass’s picture

If we have a "weight" feature in drupal_add_css() we don't need to care about your "first" or "last". If you know you need to insert something with a weight=10 in theme part it will become the very "last" and if you use weight=-10 the very first in themes part (or vice versa). This is the way how many things in drupal work and it is a way how drupal_add_css() should work, too.

If you have time to write such a patch for D7... i would love to see this implemented.

hass’s picture

Status: Active » Needs work
bwong’s picture

I need to pull down the 7.x-dev tree but I will see about implementing the patch on that. I need to setup another server to work on it.

I think we still need to determine whether the logical location (first, module, theme, last) needs to be retained or simply specify a priority. I tend to want to retain the current mode and add priorities as I have already because we would otherwise need to specify a default priority for modules and themes. The problem I see with that is having enough priorities between them to allow for proper customization. Even setting modules at 0 and themes at 100 could cause problems if someone does a module and sets its priority at 99.

Crell’s picture

IMO, there's nothing "clean" or "elegant" about !important in the first place. :-)

I still don't see the use case here. The theme, not the module, should have final absolute say in how something gets displayed. A module that is able to say "no, themes may not change the visual design here" is by definition less flexible than one that does not say that. Modules that don't have well-factored theme functions now are already a huge PITA. Providing additional ways for modules to be a PITA for themers is a bad thing. :-)

I'm tempted to won't fix this.

(Also, there is no 7.x branch yet. There won't be until 6.0 is released, at which point HEAD will become synonymous with 7.x.)

hass’s picture

What's the problem with marking a css file with a priority? This will reorder the HTML code and the theme will have the chance to do this. default weight is 0 and if you really need something different you specify a value in preprocess part of your theme. normaly i think a module css will be no problem... what have made me some stress on the theme side is - i was made to unset style.css and recreate it back - only to get the file moved to the very last position.

  $css = drupal_add_css();
  if (isset($css['all']['theme'][$vars['directory'] .'/style.css'])) {
    unset($css['all']['theme'][$vars['directory'] .'/style.css']);
    $css['all']['theme'][$vars['directory'] .'/style.css'] = TRUE;
  }

This is very bad and this would be solve able if i'm able to specify a higher weight and i'm able to move my custom drupal_add_css() files befor this style.css without unsetting and recreating the array tree. If i simply say drupal_add_css() in the theme i'm currently unable (without hacks) to get the style.css added after my 20 css files i must add before to keep css order intact.

dvessel’s picture

#1. Modules should not be concerned about what order they are in.
#2. Themes can order styles sheets through .info files.
#3. Themes that want to place a style sheet before everything can use drupal_add_css with the 'module' option and invoked directly from inside template.php.

-1 overall

hass’s picture

#2 this is not possible with dynamic CSS. I'm implementing a Theme setting with different navigations as an example (*THX* Theme settings api has become core part). This does not allow to set up this navigation css files in the .info file. I'm made to add them with drupal_add_css() and it might be possible i need to keep a order of css. CSS is Cascading Style Sheets and therefor it can overwrite each other and the correct order must be intact. today this is very difficult.

#3 this is not clean from the api way. I'm adding a theme style and not a module style

+++ for this feature.

Crell’s picture

Status: Needs work » Closed (won't fix)

hass, I don't really understand why you'd ever want to do that. If you do, it means you need to refactor your CSS in the first place.

Regardless, in Drupal 6 themes can add CSS files in any order they want, and none of them have to be named styles.css. A module that cares about its CSS order is a bug to file against that module. A module whose CSS cannot be overridden by a module is a bug to be filed against that module.

I see no use case here that is not already covered by good coding practices. Setting won't fix.

hass’s picture

Status: Closed (won't fix) » Needs work

I don't know why you don't like to make themer's live easier and why you refuse this. I'm sure you don't know about complex themes and this is why you don't understand that there are real use cases we need such a features for.

If you are able to explain me/us if it is good practice to do the following in this way or you know something better - well then "won't fix". But i think the following is far away from easy to understand for beginners and not easy to implement and more far away from "good practice" - whatever this should be. Remember we cannot workaround - or you know better - but this is the css file list an i cannot reduce this. I expect your feedback and i hope you get an idea of a complex theme... (Zen has comparable logic, but is less complex).

It works - yes, but i could be much less complex and css weight is a very important feature that is very usefull.

function _phptemplate_variables($hook, $vars = array()) {
  switch ($hook) {
    case 'page':

      // Add YAML Theme styles
      $vars['css'] = _yaml_add_css($vars);
      $vars['styles'] = _yaml_add_styles($vars);

      break;
  }

}

/**
 * Theme CSS files
 *
 * Workaround for CSS aggregate and compress feature in Drupal 5.x
 */
function _yaml_add_css($vars = array()) {

  $base_theme_directory = $vars['directory'];

  // Add core and layout specific styles
  drupal_add_css($base_theme_directory .'/yaml/core/base.css', 'theme');
  drupal_add_css($base_theme_directory .'/css/screen/basemod.css', 'theme');
  drupal_add_css($base_theme_directory .'/css/screen/basemod_2col_left_13.css', 'theme');
  drupal_add_css($base_theme_directory .'/css/screen/basemod_drupal.css', 'theme');

  // Add horizontal navigations
  $yaml_nav_primary = theme_get_setting('yaml_nav_primary');
  if ($yaml_nav_primary === 1) {
    drupal_add_css($base_theme_directory .'/css/navigation/nav_shinybuttons.css', 'theme');
  }
  else {
    drupal_add_css($base_theme_directory .'/css/navigation/nav_slidingdoor.css', 'theme');
  }

  // Add vertical navigations
  $yaml_nav_vertical = theme_get_setting('yaml_nav_vertical');
  if ($yaml_nav_vertical === 2) {
    // There are currently no additional navigations available, add here if you create your own.
  }
  else {
    drupal_add_css($base_theme_directory .'/css/navigation/nav_vlist_drupal.css', 'theme');
  }

  // Content CSS files
  drupal_add_css($base_theme_directory .'/css/screen/content.css', 'theme');

  // Add custom module styles
  _yaml_add_css_modules($base_theme_directory);

  // Add print CSS files
  drupal_add_css($base_theme_directory .'/yaml/core/print_base.css', 'theme');
  drupal_add_css($base_theme_directory .'/css/print/print_003.css', 'theme');
  drupal_add_css($base_theme_directory .'/css/print/print.css', 'theme');

  // Add debug CSS files
  if (theme_get_setting('yaml_debug') === 1) {
    drupal_add_css($base_theme_directory .'/yaml/debug/debug.css', 'theme', 'all', FALSE);
  }

  // Remove the style.css added as first array item and move it to the end
  // of the styles array. This allows to use themes style.css and keep
  // the required CSS cascading order intact for CSS overriding.
  $css = drupal_add_css();
  if (isset($css['all']['theme'][$vars['directory'] .'/style.css'])) {
    unset($css['all']['theme'][$vars['directory'] .'/style.css']);
    $css['all']['theme'][$vars['directory'] .'/style.css'] = TRUE;
  }

  // Return CSS array
  return $css;
}

function _yaml_add_styles($vars = array()) {

  $base_theme_directory = $vars['directory'];
  $output = drupal_get_css($vars['css']);

  // The styles array is complete, get the styled css and append the
  // IE Hacks. Additional this allows to dynamically add packed or other iehacks.
  $output .= "<!--[if lte IE 7]>\n";
  $output .= '<style type="text/css" media="all">@import "'. base_path() . $base_theme_directory .'/yaml/core/iehacks'. (variable_get('preprocess_css', FALSE) ? '.pack' : '') .'.css";</style>' . "\n";
  $output .= '<style type="text/css" media="all">@import "'. base_path() . $base_theme_directory .'/css/patches/patch_nav_vlist_drupal.css";</style>' . "\n";
  $output .= '<style type="text/css" media="all">@import "'. base_path() . $base_theme_directory .'/css/patches/patch_2col_left_13.css";</style>' . "\n";
  $output .= "<![endif]-->\n";

  // Page settings
  $yaml_layout_page_width_min = theme_get_setting('yaml_layout_page_width_min');
  $yaml_layout_page_width_max = theme_get_setting('yaml_layout_page_width_max');
  $yaml_layout_page_align = theme_get_setting('yaml_layout_page_align');
  if ($yaml_layout_page_align == 'left') {
    $output .= '<style type="text/css">#page_margins { margin-right: auto; margin-left: 0; min-width: '. check_plain($yaml_layout_page_width_min) .'; max-width: '. check_plain($yaml_layout_page_width_max) .' }</style>' . "\n";
  }
  elseif ($yaml_layout_page_align == 'right') {
    $output .= '<style type="text/css">#page_margins { margin-left: auto; margin-right: 0; min-width: '. check_plain($yaml_layout_page_width_min) .'; max-width: '. check_plain($yaml_layout_page_width_max) .' }</style>' . "\n";
  }
  elseif ($yaml_layout_page_align == 'center' && ($yaml_layout_page_width_min != '756px' || $yaml_layout_page_width_max != '100em')) {
    $output .= '<style type="text/css">#page_margins { min-width: '. check_plain($yaml_layout_page_width_min) .'; max-width: '. check_plain($yaml_layout_page_width_max) .' }</style>' . "\n";
  }

  // Return themed styles
  return $output;
}

function _yaml_add_css_modules($base_theme_directory = '') {

  // Add module styles if modules exists
  // The unique modules directory name should be used as CSS filename.
  if (module_exists('gallery')) {
    drupal_add_css($base_theme_directory .'/css/modules/gallery.css', 'theme');
  }
  if (module_exists('jrating')) {
    drupal_add_css($base_theme_directory .'/css/modules/jrating.css', 'theme');
  }
  if (module_exists('service_links')) {
    drupal_add_css($base_theme_directory .'/css/modules/service_links.css', 'theme');
  }
  if (module_exists('site_map') && arg(0) == 'sitemap') {
    drupal_add_css($base_theme_directory .'/css/modules/site_map.css', 'theme');
  }
  if (module_exists('project') && arg(0) == 'project') {
    drupal_add_css($base_theme_directory .'/css/modules/project.css', 'theme');
  }

  // Add non Drupal modules here.
  // To avoid naming conflicts an underscore should be added as prefix
  // to CSS filenames of modules not contributed on drupal.org.
  // if (foo == bar) {
  //   drupal_add_css($base_theme_directory .'/css/modules/_example.css', 'theme');
  // }

}

eaton’s picture

I don't know why you don't like to make themer's live easier and why you refuse this. I'm sure you don't know about complex themes and this is why you don't understand that there are real use cases we need such a features for.

Hass, please try to keep things civil. It's obvious from the discussion above that everyone here wants to make things both "easy for themers" and "well-coded." For quite some time, the philosophy that Core / Modules / Themes should be the enforced presentation hierarchy has been in place. Concern about the ripple-effects of breaking that hierarchy is not unreasonable, and grows from an understanding of Drupal from a system-wide perspective, not a lack of understanding that specific, isolated use-cases exist.

This issue has raised some good points, hopefully we can keep it focused on making Drupal the best it can be. :)

JirkaRybka’s picture

Let me sum-up my previous points: Personally, for CSS overrides, I would just use the CSS native features, and add a little more specificity to the selectors, or for a general override use the !important rule which is exactly for this purpose, no hack. This adds no extra code to the php-level, works with higer priority than the ordering of CSS files ever can, and should be understandable for a themer (because Drupal CSS-theming is already about this a lot). Sure, it'll be difficult to do a multiple-level overrides - but is that the case? I think that multiple CSS files usually doesn't all address the same elements: On per-element basis, it's mostly just Drupal's default (either system or contrib), theme's override (coming last, or higher specificity). You can easily add another override(s) with !important. If you want to rely on CSS-files ordering, then breaking the "theme last" rule is definitely NOT making themer's life easier.

And, IMO, if you want to completely rebuild CSS from scratch (your set of CSS files is terribly complicated BTW - did you consider having all CSS in, leaving the performance up to browser-cache rather than all these php-conditions?), you can do so in page.tpl.php, omitting the print $styles; line, or printing more after that.

I'm not trying to say that your approach doesn't make sense, nor to stop this issue. I just want to show that there are plenty of other options, and so I really don't see enough use case for the patch.

bwong’s picture

The main reason I want this feature is that I want control over the final presentation as the web site manager.

The !Important facility works if no one else cheats. As noted, it is one of many prioritization mechanisms available to a CSS designer.

In general, the use case for this patch is to provide the web site manager with final control over presentation without modifying any modules or themes making the changes independent of those items. It also means that changes can be easily added or removed and changes are isolated to known files. They will not be affected when a module or theme is upgraded.

For modules that have one or more CSS files, the override after the modules allows a site designer to change the styles before the themes allowing the themes to still have priority over these defaults. In this case, the override would provides the desired results if the theme does not, often because the theme does not take a particular module into account. If the !Important approach is used then it would override the theme and that is not the desired result in this case. The placement of a CSS file between the usual module CSS files and the theme CSS files does.

Likewise, CSS files added after themes and order may still be important. For example, if I want to create a customization of a particular theme I can provide a CSS file that will go after the theme's CSS files. Subsequent CSS files can override these changes. Typically changes are a subset, not a full replacement so order is the easiest way to address the problem.

I have not finished it yet but having a module that can arbitrarily incorporate CSS files associated with a particular module or theme would be quite handy. With proper security support, it would allow a person with a particular role to manage these files without having access to the rest of the system.

In general, I don't want anyone mucking around with files or PHP if they don't have to. The biggest problem I have with customized sites is that maintaining, recreating and upgrading is a royal pain if changes are not made in an orderly and isolated fashion.

dvessel’s picture

Status: Needs work » Closed (won't fix)

What your asking for will never happen. Everyone behind Drupal is more concerned with making theming easier for designers. Your or anyones edge case will not justify such a big change.

With some ingenuity you can accomplish what you want. I'd imagine a theme engine to intercept the render and pushing in your styles. I think it'd be pretty simple. It doesn't take a lot to create one. All you'd need to do is set the engine to your own for each theme's .info file.

jamesbecker’s picture

Issue summary: View changes

I've run into an excellent use case for this patch: Drupal language switcher RTL language theming. In almost all cases, the modified rendering that Drupal outputs for RTL languages is imperfect. The solution I moved forward with was to create duplicate CSS files which are loaded only if the global $language->language variable is set to an RTL language.

Everything worked well with this approach; however, drupal_add_css() loads the files *before* the CSS files referred to in the theme.info file. This necessitated the use of the !important rule in each desired CSS property in the RTL CSS files.

With this patch, I can load the RTL-language-specific CSS files after the default theme ones, which allows me to override properties without the !important rule.