Discovered over in #2656548: Fully support PHP 7.0 in Drupal 7 if CSS is overridden more than once then the it's weight won't be correctly updated, which means the wrong file may be returned.

Comments

MustangGB created an issue. See original summary.

mustanggb’s picture

Status: Active » Needs review
StatusFileSize
new1.31 KB

And a patch.

mustanggb’s picture

The test which this fixes for PHP7 is testRenderOverride, which is now passing.

klausi’s picture

Status: Needs review » Needs work
Issue tags: +PHP 7.0 (duplicate)

The root cause of this is that uasort() behavior has changed in PHP 7, which is called in drupal_get_css(). When 2 weights are equal, then we start to see different results in PHP 5 and PHP 7.

uasort() WTF: https://3v4l.org/mYVu1

I think the fix is the correct approach, because we want to make the weight unique in any case in druapl_add_css().

  1. +++ b/includes/common.inc
    @@ -3025,6 +3025,7 @@ function drupal_add_html_head_link($attributes, $header = FALSE) {
       $css = &drupal_static(__FUNCTION__, array());
    +  $count = &drupal_static(__FUNCTION__ . '_count', 0);
    

    We need to reset this counter every time drupal_add_css is reset. Please search the code base where that happens, one instance is in CascadingStylesheetsTestCase for example.

  2. +++ b/includes/common.inc
    @@ -3068,11 +3069,13 @@ function drupal_add_css($data = NULL, $options = NULL) {
             $css[] = $options;
    +        $count++;
             break;
           default:
             // Local and external files must keep their name as the associative key
             // so the same CSS file is not be added twice.
             $css[$data] = $options;
    +        $count++;
    

    no need to have this in 2 places, just add it right under the call where you use $count.

klausi’s picture

Better idea: instead requiring drupal_static_reset('drupal_add_css_count') everywhere in core and contrib we can reset the count whenever count($css) is 0. When there are zero CSS files added then we can also start from zero with the counter.

klausi’s picture

Status: Needs work » Needs review
StatusFileSize
new989 bytes

This is what I have in mind.

mustanggb’s picture

Status: Needs review » Reviewed & tested by the community

Yes this is stemming from uasort(), but technically the definition hasn't changed as the sort order of equally weighted items was always undefined, so it was wrong to rely on it.

Good catch on the reset thing.

And the reason for the duplicate $count++; was that at the time I hadn't seen that the switch statement contained a default, I noticed soon after uploading the patch, so yes it's fine to remove the duplicate code.

Looks good to me; RTBC.

fabianx’s picture

This is a bigger core bug in itself, the count($css) is totally wrong, so I am fine with the solution here.

Note: There is an issue to make uasort a nice helper that always works the same.

stefan.r’s picture

Issue tags: +Pending Drupal 7 commit
stefan.r’s picture

fabianx’s picture

I checked that we did not have to fix this in Drupal 8 as _drupal_add_css is just called once in the end.

David_Rothstein’s picture

Status: Reviewed & tested by the community » Fixed
Issue tags: -Pending Drupal 7 commit

Committed to 7.x - thanks!

I'm not totally convinced this doesn't need to be fixed in Drupal 8... but at least it doesn't break tests there. And for Drupal 7 (and perhaps Drupal 8 too) I think we need a similar fix for drupal_add_js(). But since this is the only one that is causing test failures, committing it to make progress and leaving the rest to #2380655: Make CSS/JS order with the same weight deterministic (or another issue?) seems fine to me.

I changed the code comment on commit, to make it more like the comment for an existing static variable in drupal_add_js(), and to hopefully better explain why we need this:

diff --git a/includes/common.inc b/includes/common.inc
index 442e0f3..f7180f4 100644
--- a/includes/common.inc
+++ b/includes/common.inc
@@ -3036,7 +3036,8 @@ function drupal_add_css($data = NULL, $options = NULL) {
   $css = &drupal_static(__FUNCTION__, array());
   $count = &drupal_static(__FUNCTION__ . '_count', 0);
 
-  // Reset the order counter every time the number of entries in $css is 0.
+  // If the $css variable has been reset with drupal_static_reset(), there is
+  // no longer any CSS being tracked, so set the counter back to 0 also.
   if (count($css) === 0) {
     $count = 0;
   }

  • David_Rothstein committed 9bf8fdf on 7.x
    Issue #2712993 by MustangGB, klausi: Can't override the same CSS files...

Status: Fixed » Closed (fixed)

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