This issue was encountered on a Drupal 6 installation, but the code suggests it might apply to Drupal 7 as well:

  1. drupal_find_theme_templates() calls drupal_system_listing(), using the filename to index the results.
  2. drupal_system_listing() in turn calls file_scan_directory() which is dependent on the order in which readdir() returns files and uses depth to determine wether a match should replace a previous match.
  3. readdir() returns files in the order in which they have been stored in the file system.

Given the following two files:

  • parent_theme/templates/global/page.tpl.php
  • parent_theme/subthemes/child_theme/page.tpl.php

, page.tpl.php would no longer get picked up for the parent theme after a backup was restored. Debugging revealed that file_scan_directory would consistently return the page.tpl.php file of the child theme.
In this scenario restoring the backup changed the storage order of the involved files, thus causing the bug.

Comments

ciss created an issue. See original summary.

donquixote’s picture

I agree with this concern.

Just to clarify:
file_scan_directory() does give priority to files in the parent folder.
But it does not have a predictable way to prioritize one subfolder over another. It cannot know that "/templates/" belongs to the parent theme, but "/subthemes/child_theme/" belongs to the sub-theme.
The only way to make this distinction would be to look for *.info files in subfolders, and stop there.

Imo, file_scan_directory() is doing too many things at once.
It would be easier to fix this, if we knew that this function was only used to scan for theme templates.
Since it is used for all kinds of arbitrary and unrelated stuff (also in contrib), we need to be super careful for side effects.

ciss’s picture

Title: drupal_find_theme_templates() may be unreliable for nested themes » drupal_find_theme_templates() may be unreliable when using nested themes
dave reid’s picture

Version: 7.x-dev » 8.2.x-dev

Confirmed this bug in D8 and D7. This happens because drupal_system_listing() is called to index the results by 'name', which if there are multiple templates with the same name in the directory, will only return one result. This will be a problem when the first result found is not the desired theme that is actually being searched.

Given this structure:

mytheme/
  - mysubtheme/
    - templates/
      - page.tpl.php
  - mytheme.info
  - templates/
    - page.tpl.php

The results of drupal_system_listing('/\.tpl\.php$', 'sites/all/themes/contrib/mytheme', 'name', 0) only returns one result, which is unexpected, there were two files that ended with .tpl.php!

array (
  'page.tpl' => 
  stdClass::__set_state(array(
     'uri' => 'sites/all/themes/contrib/mytheme/mysubtheme/templates/page.tpl.php',
     'filename' => 'page.tpl.php',
     'name' => 'page.tpl',
  )),
)

Now, if we change the 'name' parameter to 'uri' to ensure that results with the same filename are included, we do get two results

array (
  'sites/all/themes/contrib/mytheme/templates/page.tpl.php' => 
  stdClass::__set_state(array(
     'uri' => 'sites/all/themes/contrib/mytheme/templates/page.tpl.php',
     'filename' => 'page.tpl.php',
     'name' => 'page.tpl',
  )),
  'sites/all/themes/contrib/mytheme/mysubtheme/templates/page.tpl.php' => 
  stdClass::__set_state(array(
     'uri' => 'sites/all/themes/contrib/mytheme/mysubtheme/templates/page.tpl.php',
     'filename' => 'page.tpl.php',
     'name' => 'page.tpl',
  )),
)

Note that what happened before is that since sites/all/themes/contrib/mytheme/mysubtheme/templates/page.tpl.php was found second, it overwrote the first result, since the return value was keyed by filename, and both filenames were 'page.tpl.php'. What happens originally is then since the one result returned was in a subtheme path, it was excluded as a candidate template for page.tpl.php, leaving no more results. Which then falls back to using modules/system/page.tpl.php, when we just wanted to use sites/all/themes/contrib/mytheme/templates/page.tpl.php!

dave reid’s picture

Patches for D8 and D7 attached. Likely needs tests.

dave reid’s picture

Status: Active » Needs review

Status: Needs review » Needs work

Version: 8.2.x-dev » 8.3.x-dev

Drupal 8.2.0-beta1 was released on August 3, 2016, which means new developments and disruptive changes should now be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.0-alpha1 will be released the week of January 30, 2017, which means new developments and disruptive changes should now be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.0-alpha1 will be released the week of July 31, 2017, which means new developments and disruptive changes should now be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.0-alpha1 will be released the week of January 17, 2018, which means new developments and disruptive changes should now be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.6.x-dev » 8.7.x-dev

Drupal 8.6.0-alpha1 will be released the week of July 16, 2018, which means new developments and disruptive changes should now be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

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.

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

jkdev’s picture

7 years later, the issue still exist.

Only this time this is in: \Drupal\Core\File\FileSystem::doScanDirectory at web/core/lib/Drupal/Core/File/FileSystem.php:707:

if ($handle = @opendir($dir)) {
      while (FALSE !== ($filename = readdir($handle))) {

Can we work on this, so that in ext4 filesystems, this will not mess up themes?

More info about my situation are here: #3329810, #3316641 (with full easy reproduce)

Can we use scandir instead?

(scandir(string $directory, int $sorting_order = SCANDIR_SORT_ASCENDING, ?resource $context = null): array|false)

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.